001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.Arrays;
023import java.util.BitSet;
024
025import com.puppycrawl.tools.checkstyle.PropertyType;
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
032import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
033import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
034import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
035
036/**
037 * <div>
038 * Checks that there are no
039 * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
040 * &quot;magic numbers&quot;</a> where a magic
041 * number is a numeric literal that is not defined as a constant.
042 * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
043 * </div>
044 *
045 * <p>Constant definition is any variable/field that has 'final' modifier.
046 * It is fine to have one constant defining multiple numeric literals within one expression:
047 * </p>
048 * <pre>
049 * static final int SECONDS_PER_DAY = 24 * 60 * 60;
050 * static final double SPECIAL_RATIO = 4.0 / 3.0;
051 * static final double SPECIAL_SUM = 1 + Math.E;
052 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
053 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
054 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);
055 * </pre>
056 * <ul>
057 * <li>
058 * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path
059 * from the number literal to the enclosing constant definition.
060 * Type is {@code java.lang.String[]}.
061 * Validation type is {@code tokenTypesSet}.
062 * Default value is
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
064 * ARRAY_INIT</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
066 * ASSIGN</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
068 * DIV</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST">
070 * ELIST</a>,
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
072 * EXPR</a>,
073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
074 * LITERAL_NEW</a>,
075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
076 * METHOD_CALL</a>,
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
078 * MINUS</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
080 * PLUS</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
082 * STAR</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
084 * TYPECAST</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
086 * UNARY_MINUS</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
088 * UNARY_PLUS</a>.
089 * </li>
090 * <li>
091 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations.
092 * Type is {@code boolean}.
093 * Default value is {@code false}.
094 * </li>
095 * <li>
096 * Property {@code ignoreAnnotationElementDefaults} -
097 * Ignore magic numbers in annotation elements defaults.
098 * Type is {@code boolean}.
099 * Default value is {@code true}.
100 * </li>
101 * <li>
102 * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations.
103 * Type is {@code boolean}.
104 * Default value is {@code false}.
105 * </li>
106 * <li>
107 * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods.
108 * Type is {@code boolean}.
109 * Default value is {@code false}.
110 * </li>
111 * <li>
112 * Property {@code ignoreNumbers} - Specify non-magic numbers.
113 * Type is {@code double[]}.
114 * Default value is {@code -1, 0, 1, 2}.
115 * </li>
116 * <li>
117 * Property {@code tokens} - tokens to check
118 * Type is {@code java.lang.String[]}.
119 * Validation type is {@code tokenSet}.
120 * Default value is:
121 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
122 * NUM_DOUBLE</a>,
123 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
124 * NUM_FLOAT</a>,
125 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
126 * NUM_INT</a>,
127 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
128 * NUM_LONG</a>.
129 * </li>
130 * </ul>
131 *
132 * <p>
133 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
134 * </p>
135 *
136 * <p>
137 * Violation Message Keys:
138 * </p>
139 * <ul>
140 * <li>
141 * {@code magic.number}
142 * </li>
143 * </ul>
144 *
145 * @since 3.1
146 */
147@StatelessCheck
148public class MagicNumberCheck extends AbstractCheck {
149
150    /**
151     * A key is pointing to the warning message text in "messages.properties"
152     * file.
153     */
154    public static final String MSG_KEY = "magic.number";
155
156    /**
157     * Specify tokens that are allowed in the AST path from the
158     * number literal to the enclosing constant definition.
159     */
160    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
161    private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
162        TokenTypes.ASSIGN,
163        TokenTypes.ARRAY_INIT,
164        TokenTypes.EXPR,
165        TokenTypes.UNARY_PLUS,
166        TokenTypes.UNARY_MINUS,
167        TokenTypes.TYPECAST,
168        TokenTypes.ELIST,
169        TokenTypes.LITERAL_NEW,
170        TokenTypes.METHOD_CALL,
171        TokenTypes.STAR,
172        TokenTypes.DIV,
173        TokenTypes.PLUS,
174        TokenTypes.MINUS
175    );
176
177    /** Specify non-magic numbers. */
178    private double[] ignoreNumbers = {-1, 0, 1, 2};
179
180    /** Ignore magic numbers in hashCode methods. */
181    private boolean ignoreHashCodeMethod;
182
183    /** Ignore magic numbers in annotation declarations. */
184    private boolean ignoreAnnotation;
185
186    /** Ignore magic numbers in field declarations. */
187    private boolean ignoreFieldDeclaration;
188
189    /** Ignore magic numbers in annotation elements defaults. */
190    private boolean ignoreAnnotationElementDefaults = true;
191
192    @Override
193    public int[] getDefaultTokens() {
194        return getAcceptableTokens();
195    }
196
197    @Override
198    public int[] getAcceptableTokens() {
199        return new int[] {
200            TokenTypes.NUM_DOUBLE,
201            TokenTypes.NUM_FLOAT,
202            TokenTypes.NUM_INT,
203            TokenTypes.NUM_LONG,
204        };
205    }
206
207    @Override
208    public int[] getRequiredTokens() {
209        return CommonUtil.EMPTY_INT_ARRAY;
210    }
211
212    @Override
213    public void visitToken(DetailAST ast) {
214        if (shouldTestAnnotationArgs(ast)
215                && shouldTestAnnotationDefaults(ast)
216                && !isInIgnoreList(ast)
217                && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
218            final DetailAST constantDefAST = findContainingConstantDef(ast);
219
220            if (constantDefAST == null) {
221                if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
222                    reportMagicNumber(ast);
223                }
224            }
225            else {
226                final boolean found = isMagicNumberExists(ast, constantDefAST);
227                if (found) {
228                    reportMagicNumber(ast);
229                }
230            }
231        }
232    }
233
234    /**
235     * Checks if ast is annotation argument and should be checked.
236     *
237     * @param ast token to check
238     * @return true if element is skipped, false otherwise
239     */
240    private boolean shouldTestAnnotationArgs(DetailAST ast) {
241        return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
242    }
243
244    /**
245     * Checks if ast is annotation element default value and should be checked.
246     *
247     * @param ast token to check
248     * @return true if element is skipped, false otherwise
249     */
250    private boolean shouldTestAnnotationDefaults(DetailAST ast) {
251        return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
252    }
253
254    /**
255     * Is magic number somewhere at ast tree.
256     *
257     * @param ast ast token
258     * @param constantDefAST constant ast
259     * @return true if magic number is present
260     */
261    private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
262        boolean found = false;
263        DetailAST astNode = ast.getParent();
264        while (astNode != constantDefAST) {
265            final int type = astNode.getType();
266            if (!constantWaiverParentToken.get(type)) {
267                found = true;
268                break;
269            }
270            astNode = astNode.getParent();
271        }
272        return found;
273    }
274
275    /**
276     * Finds the constant definition that contains aAST.
277     *
278     * @param ast the AST
279     * @return the constant def or null if ast is not contained in a constant definition.
280     */
281    private static DetailAST findContainingConstantDef(DetailAST ast) {
282        DetailAST varDefAST = ast;
283        while (varDefAST != null
284                && varDefAST.getType() != TokenTypes.VARIABLE_DEF
285                && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
286            varDefAST = varDefAST.getParent();
287        }
288        DetailAST constantDef = null;
289
290        // no containing variable definition?
291        if (varDefAST != null) {
292            // implicit constant?
293            if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
294                    || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
295                constantDef = varDefAST;
296            }
297            else {
298                // explicit constant
299                final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
300
301                if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
302                    constantDef = varDefAST;
303                }
304            }
305        }
306        return constantDef;
307    }
308
309    /**
310     * Reports aAST as a magic number, includes unary operators as needed.
311     *
312     * @param ast the AST node that contains the number to report
313     */
314    private void reportMagicNumber(DetailAST ast) {
315        String text = ast.getText();
316        final DetailAST parent = ast.getParent();
317        DetailAST reportAST = ast;
318        if (parent.getType() == TokenTypes.UNARY_MINUS) {
319            reportAST = parent;
320            text = "-" + text;
321        }
322        else if (parent.getType() == TokenTypes.UNARY_PLUS) {
323            reportAST = parent;
324            text = "+" + text;
325        }
326        log(reportAST,
327                MSG_KEY,
328                text);
329    }
330
331    /**
332     * Determines whether or not the given AST is in a valid hash code method.
333     * A valid hash code method is considered to be a method of the signature
334     * {@code public int hashCode()}.
335     *
336     * @param ast the AST from which to search for an enclosing hash code
337     *     method definition
338     *
339     * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
340     */
341    private static boolean isInHashCodeMethod(DetailAST ast) {
342        // find the method definition AST
343        DetailAST currentAST = ast;
344        while (currentAST != null
345                && currentAST.getType() != TokenTypes.METHOD_DEF) {
346            currentAST = currentAST.getParent();
347        }
348        final DetailAST methodDefAST = currentAST;
349        boolean inHashCodeMethod = false;
350
351        if (methodDefAST != null) {
352            // Check for 'hashCode' name.
353            final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
354
355            if ("hashCode".equals(identAST.getText())) {
356                // Check for no arguments.
357                final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
358                // we are in a 'public int hashCode()' method! The compiler will ensure
359                // the method returns an 'int' and is public.
360                inHashCodeMethod = !paramAST.hasChildren();
361            }
362        }
363        return inHashCodeMethod;
364    }
365
366    /**
367     * Decides whether the number of an AST is in the ignore list of this
368     * check.
369     *
370     * @param ast the AST to check
371     * @return true if the number of ast is in the ignore list of this check.
372     */
373    private boolean isInIgnoreList(DetailAST ast) {
374        double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
375        final DetailAST parent = ast.getParent();
376        if (parent.getType() == TokenTypes.UNARY_MINUS) {
377            value = -1 * value;
378        }
379        return Arrays.binarySearch(ignoreNumbers, value) >= 0;
380    }
381
382    /**
383     * Determines whether or not the given AST is field declaration.
384     *
385     * @param ast AST from which to search for an enclosing field declaration
386     *
387     * @return {@code true} if {@code ast} is in the scope of field declaration
388     */
389    private static boolean isFieldDeclaration(DetailAST ast) {
390        DetailAST varDefAST = null;
391        DetailAST node = ast;
392        while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
393            if (node.getType() == TokenTypes.VARIABLE_DEF) {
394                varDefAST = node;
395                break;
396            }
397            node = node.getParent();
398        }
399
400        // contains variable declaration
401        // and it is directly inside class or record declaration
402        return varDefAST != null
403                && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
404                || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF
405                || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW);
406    }
407
408    /**
409     * Setter to specify tokens that are allowed in the AST path from the
410     * number literal to the enclosing constant definition.
411     *
412     * @param tokens The string representation of the tokens interested in
413     * @since 6.11
414     */
415    public void setConstantWaiverParentToken(String... tokens) {
416        constantWaiverParentToken = TokenUtil.asBitSet(tokens);
417    }
418
419    /**
420     * Setter to specify non-magic numbers.
421     *
422     * @param list numbers to ignore.
423     * @since 3.1
424     */
425    public void setIgnoreNumbers(double... list) {
426        ignoreNumbers = new double[list.length];
427        System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
428        Arrays.sort(ignoreNumbers);
429    }
430
431    /**
432     * Setter to ignore magic numbers in hashCode methods.
433     *
434     * @param ignoreHashCodeMethod decide whether to ignore
435     *     hash code methods
436     * @since 5.3
437     */
438    public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
439        this.ignoreHashCodeMethod = ignoreHashCodeMethod;
440    }
441
442    /**
443     * Setter to ignore magic numbers in annotation declarations.
444     *
445     * @param ignoreAnnotation decide whether to ignore annotations
446     * @since 5.4
447     */
448    public void setIgnoreAnnotation(boolean ignoreAnnotation) {
449        this.ignoreAnnotation = ignoreAnnotation;
450    }
451
452    /**
453     * Setter to ignore magic numbers in field declarations.
454     *
455     * @param ignoreFieldDeclaration decide whether to ignore magic numbers
456     *     in field declaration
457     * @since 6.6
458     */
459    public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
460        this.ignoreFieldDeclaration = ignoreFieldDeclaration;
461    }
462
463    /**
464     * Setter to ignore magic numbers in annotation elements defaults.
465     *
466     * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
467     * @since 8.23
468     */
469    public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
470        this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
471    }
472
473    /**
474     * Determines if the given AST node has a parent node with given token type code.
475     *
476     * @param ast the AST from which to search for annotations
477     * @param type the type code of parent token
478     *
479     * @return {@code true} if the AST node has a parent with given token type.
480     */
481    private static boolean isChildOf(DetailAST ast, int type) {
482        boolean result = false;
483        DetailAST node = ast;
484        do {
485            if (node.getType() == type) {
486                result = true;
487                break;
488            }
489            node = node.getParent();
490        } while (node != null);
491
492        return result;
493    }
494
495}