001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <p>
035 * Checks that there are no
036 * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
037 * &quot;magic numbers&quot;</a> where a magic
038 * number is a numeric literal that is not defined as a constant.
039 * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
040 * </p>
041 *
042 * <p>Constant definition is any variable/field that has 'final' modifier.
043 * It is fine to have one constant defining multiple numeric literals within one expression:
044 * </p>
045 * <pre>
046 * static final int SECONDS_PER_DAY = 24 * 60 * 60;
047 * static final double SPECIAL_RATIO = 4.0 / 3.0;
048 * static final double SPECIAL_SUM = 1 + Math.E;
049 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
050 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
051 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);
052 * </pre>
053 * <ul>
054 * <li>
055 * Property {@code ignoreNumbers} - Specify non-magic numbers.
056 * Type is {@code double[]}.
057 * Default value is {@code -1, 0, 1, 2}.
058 * </li>
059 * <li>
060 * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods.
061 * Type is {@code boolean}.
062 * Default value is {@code false}.
063 * </li>
064 * <li>
065 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations.
066 * Type is {@code boolean}.
067 * Default value is {@code false}.
068 * </li>
069 * <li>
070 * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations.
071 * Type is {@code boolean}.
072 * Default value is {@code false}.
073 * </li>
074 * <li>
075 * Property {@code ignoreAnnotationElementDefaults} -
076 * Ignore magic numbers in annotation elements defaults.
077 * Type is {@code boolean}.
078 * Default value is {@code true}.
079 * </li>
080 * <li>
081 * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path
082 * from the number literal to the enclosing constant definition.
083 * Type is {@code java.lang.String[]}.
084 * Validation type is {@code tokenTypesSet}.
085 * Default value is
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
087 * TYPECAST</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
089 * METHOD_CALL</a>,
090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
091 * EXPR</a>,
092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
093 * ARRAY_INIT</a>,
094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
095 * UNARY_MINUS</a>,
096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
097 * UNARY_PLUS</a>,
098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST">
099 * ELIST</a>,
100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
101 * STAR</a>,
102 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
103 * ASSIGN</a>,
104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
105 * PLUS</a>,
106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
107 * MINUS</a>,
108 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
109 * DIV</a>,
110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
111 * LITERAL_NEW</a>.
112 * </li>
113 * <li>
114 * Property {@code tokens} - tokens to check
115 * Type is {@code java.lang.String[]}.
116 * Validation type is {@code tokenSet}.
117 * Default value is:
118 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
119 * NUM_DOUBLE</a>,
120 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
121 * NUM_FLOAT</a>,
122 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
123 * NUM_INT</a>,
124 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
125 * NUM_LONG</a>.
126 * </li>
127 * </ul>
128 * <p>
129 * To configure the check with default configuration:
130 * </p>
131 * <pre>
132 * &lt;module name=&quot;MagicNumber&quot;/&gt;
133 * </pre>
134 * <p>
135 * results is following violations:
136 * </p>
137 * <pre>
138 * &#64;MyAnnotation(6) // violation
139 * class MyClass {
140 *   private field = 7; // violation
141 *
142 *   void foo() {
143 *     int i = i + 1; // no violation
144 *     int j = j + 8; // violation
145 *   }
146 *
147 *   public int hashCode() {
148 *     return 10;    // violation
149 *   }
150 * }
151 * &#64;interface anno {
152 *   int value() default 10; // no violation
153 * }
154 * </pre>
155 * <p>
156 * To configure the check so that it checks floating-point numbers
157 * that are not 0, 0.5, or 1:
158 * </p>
159 * <pre>
160 * &lt;module name=&quot;MagicNumber&quot;&gt;
161 *   &lt;property name=&quot;tokens&quot; value=&quot;NUM_DOUBLE, NUM_FLOAT&quot;/&gt;
162 *   &lt;property name=&quot;ignoreNumbers&quot; value=&quot;0, 0.5, 1&quot;/&gt;
163 *   &lt;property name=&quot;ignoreFieldDeclaration&quot; value=&quot;true&quot;/&gt;
164 *   &lt;property name=&quot;ignoreAnnotation&quot; value=&quot;true&quot;/&gt;
165 * &lt;/module&gt;
166 * </pre>
167 * <p>
168 * results is following violations:
169 * </p>
170 * <pre>
171 * &#64;MyAnnotation(6) // no violation
172 * class MyClass {
173 *   private field = 7; // no violation
174 *
175 *   void foo() {
176 *     int i = i + 1; // no violation
177 *     int j = j + 8; // violation
178 *   }
179 * }
180 * </pre>
181 * <p>
182 * To configure the check so that it ignores magic numbers in field declarations:
183 * </p>
184 * <pre>
185 * &lt;module name=&quot;MagicNumber&quot;&gt;
186 *   &lt;property name=&quot;ignoreFieldDeclaration&quot; value=&quot;false&quot;/&gt;
187 * &lt;/module&gt;
188 * </pre>
189 * <p>
190 * results in the following violations:
191 * </p>
192 * <pre>
193 * public record MyRecord() {
194 *     private static int myInt = 7; // ok, field declaration
195 *
196 *     void foo() {
197 *         int i = myInt + 1; // no violation, 1 is defined as non-magic
198 *         int j = myInt + 8; // violation
199 *     }
200 * }
201 * </pre>
202 * <p>
203 * To configure the check to check annotation element defaults:
204 * </p>
205 * <pre>
206 * &lt;module name=&quot;MagicNumber&quot;&gt;
207 *   &lt;property name=&quot;ignoreAnnotationElementDefaults&quot; value=&quot;false&quot;/&gt;
208 * &lt;/module&gt;
209 * </pre>
210 * <p>
211 * results in following violations:
212 * </p>
213 * <pre>
214 * &#64;interface anno {
215 *   int value() default 10; // violation
216 *   int[] value2() default {10}; // violation
217 * }
218 * </pre>
219 * <p>
220 * Config example of constantWaiverParentToken option:
221 * </p>
222 * <pre>
223 * &lt;module name=&quot;MagicNumber&quot;&gt;
224 *   &lt;property name=&quot;constantWaiverParentToken&quot; value=&quot;ASSIGN,ARRAY_INIT,EXPR,
225 *   UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS &quot;/&gt;
226 * &lt;/module&gt;
227 * </pre>
228 * <p>
229 * result is following violation:
230 * </p>
231 * <pre>
232 * class TestMethodCall {
233 *   public void method2() {
234 *     final TestMethodCall dummyObject = new TestMethodCall(62);    //violation
235 *     final int a = 3;        // ok as waiver is ASSIGN
236 *     final int [] b = {4, 5} // ok as waiver is ARRAY_INIT
237 *     final int c = -3;       // ok as waiver is UNARY_MINUS
238 *     final int d = +4;       // ok as waiver is UNARY_PLUS
239 *     final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL
240 *     final int x = 3 * 4;    // violation
241 *     final int y = 3 / 4;    // ok as waiver is DIV
242 *     final int z = 3 + 4;    // ok as waiver is PLUS
243 *     final int w = 3 - 4;    // violation
244 *     final int x = (int)(3.4);    //ok as waiver is TYPECAST
245 *   }
246 * }
247 * </pre>
248 *
249 * <p>
250 * Config example of ignoreHashCodeMethod option:
251 * </p>
252 * <pre>
253 * &lt;module name=&quot;MagicNumber&quot;&gt;
254 *   &lt;property name=&quot;ignoreHashCodeMethod&quot; value=&quot;true&quot;/&gt;
255 * &lt;/module&gt;
256 * </pre>
257 * <p>
258 * result is no violation:
259 * </p>
260 * <pre>
261 * class TestHashCode {
262 *     public int hashCode() {
263 *         return 10;       // OK
264 *     }
265 * }
266 * </pre>
267 * <p>
268 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
269 * </p>
270 * <p>
271 * Violation Message Keys:
272 * </p>
273 * <ul>
274 * <li>
275 * {@code magic.number}
276 * </li>
277 * </ul>
278 *
279 * @since 3.1
280 */
281@StatelessCheck
282public class MagicNumberCheck extends AbstractCheck {
283
284    /**
285     * A key is pointing to the warning message text in "messages.properties"
286     * file.
287     */
288    public static final String MSG_KEY = "magic.number";
289
290    /**
291     * Specify tokens that are allowed in the AST path from the
292     * number literal to the enclosing constant definition.
293     */
294    private int[] constantWaiverParentToken = {
295        TokenTypes.ASSIGN,
296        TokenTypes.ARRAY_INIT,
297        TokenTypes.EXPR,
298        TokenTypes.UNARY_PLUS,
299        TokenTypes.UNARY_MINUS,
300        TokenTypes.TYPECAST,
301        TokenTypes.ELIST,
302        TokenTypes.LITERAL_NEW,
303        TokenTypes.METHOD_CALL,
304        TokenTypes.STAR,
305        TokenTypes.DIV,
306        TokenTypes.PLUS,
307        TokenTypes.MINUS,
308    };
309
310    /** Specify non-magic numbers. */
311    private double[] ignoreNumbers = {-1, 0, 1, 2};
312
313    /** Ignore magic numbers in hashCode methods. */
314    private boolean ignoreHashCodeMethod;
315
316    /** Ignore magic numbers in annotation declarations. */
317    private boolean ignoreAnnotation;
318
319    /** Ignore magic numbers in field declarations. */
320    private boolean ignoreFieldDeclaration;
321
322    /** Ignore magic numbers in annotation elements defaults. */
323    private boolean ignoreAnnotationElementDefaults = true;
324
325    /**
326     * Constructor for MagicNumber Check.
327     * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search.
328     */
329    public MagicNumberCheck() {
330        Arrays.sort(constantWaiverParentToken);
331    }
332
333    @Override
334    public int[] getDefaultTokens() {
335        return getAcceptableTokens();
336    }
337
338    @Override
339    public int[] getAcceptableTokens() {
340        return new int[] {
341            TokenTypes.NUM_DOUBLE,
342            TokenTypes.NUM_FLOAT,
343            TokenTypes.NUM_INT,
344            TokenTypes.NUM_LONG,
345        };
346    }
347
348    @Override
349    public int[] getRequiredTokens() {
350        return CommonUtil.EMPTY_INT_ARRAY;
351    }
352
353    @Override
354    public void visitToken(DetailAST ast) {
355        if (shouldTestAnnotationArgs(ast)
356                && shouldTestAnnotationDefaults(ast)
357                && !isInIgnoreList(ast)
358                && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
359            final DetailAST constantDefAST = findContainingConstantDef(ast);
360
361            if (constantDefAST == null) {
362                if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
363                    reportMagicNumber(ast);
364                }
365            }
366            else {
367                final boolean found = isMagicNumberExists(ast, constantDefAST);
368                if (found) {
369                    reportMagicNumber(ast);
370                }
371            }
372        }
373    }
374
375    /**
376     * Checks if ast is annotation argument and should be checked.
377     *
378     * @param ast token to check
379     * @return true if element is skipped, false otherwise
380     */
381    private boolean shouldTestAnnotationArgs(DetailAST ast) {
382        return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
383    }
384
385    /**
386     * Checks if ast is annotation element default value and should be checked.
387     *
388     * @param ast token to check
389     * @return true if element is skipped, false otherwise
390     */
391    private boolean shouldTestAnnotationDefaults(DetailAST ast) {
392        return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
393    }
394
395    /**
396     * Is magic number some where at ast tree.
397     *
398     * @param ast ast token
399     * @param constantDefAST constant ast
400     * @return true if magic number is present
401     */
402    private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
403        boolean found = false;
404        DetailAST astNode = ast.getParent();
405        while (astNode != constantDefAST) {
406            final int type = astNode.getType();
407            if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) {
408                found = true;
409                break;
410            }
411            astNode = astNode.getParent();
412        }
413        return found;
414    }
415
416    /**
417     * Finds the constant definition that contains aAST.
418     *
419     * @param ast the AST
420     * @return the constant def or null if ast is not contained in a constant definition.
421     */
422    private static DetailAST findContainingConstantDef(DetailAST ast) {
423        DetailAST varDefAST = ast;
424        while (varDefAST != null
425                && varDefAST.getType() != TokenTypes.VARIABLE_DEF
426                && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
427            varDefAST = varDefAST.getParent();
428        }
429        DetailAST constantDef = null;
430
431        // no containing variable definition?
432        if (varDefAST != null) {
433            // implicit constant?
434            if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
435                    || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
436                constantDef = varDefAST;
437            }
438            else {
439                // explicit constant
440                final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
441
442                if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
443                    constantDef = varDefAST;
444                }
445            }
446        }
447        return constantDef;
448    }
449
450    /**
451     * Reports aAST as a magic number, includes unary operators as needed.
452     *
453     * @param ast the AST node that contains the number to report
454     */
455    private void reportMagicNumber(DetailAST ast) {
456        String text = ast.getText();
457        final DetailAST parent = ast.getParent();
458        DetailAST reportAST = ast;
459        if (parent.getType() == TokenTypes.UNARY_MINUS) {
460            reportAST = parent;
461            text = "-" + text;
462        }
463        else if (parent.getType() == TokenTypes.UNARY_PLUS) {
464            reportAST = parent;
465            text = "+" + text;
466        }
467        log(reportAST,
468                MSG_KEY,
469                text);
470    }
471
472    /**
473     * Determines whether or not the given AST is in a valid hash code method.
474     * A valid hash code method is considered to be a method of the signature
475     * {@code public int hashCode()}.
476     *
477     * @param ast the AST from which to search for an enclosing hash code
478     *     method definition
479     *
480     * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
481     */
482    private static boolean isInHashCodeMethod(DetailAST ast) {
483        boolean inHashCodeMethod = false;
484
485        // if not in a code block, can't be in hashCode()
486        if (ScopeUtil.isInCodeBlock(ast)) {
487            // find the method definition AST
488            DetailAST methodDefAST = ast.getParent();
489            while (methodDefAST != null
490                    && methodDefAST.getType() != TokenTypes.METHOD_DEF) {
491                methodDefAST = methodDefAST.getParent();
492            }
493
494            if (methodDefAST != null) {
495                // Check for 'hashCode' name.
496                final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
497
498                if ("hashCode".equals(identAST.getText())) {
499                    // Check for no arguments.
500                    final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
501                    // we are in a 'public int hashCode()' method! The compiler will ensure
502                    // the method returns an 'int' and is public.
503                    inHashCodeMethod = !paramAST.hasChildren();
504                }
505            }
506        }
507        return inHashCodeMethod;
508    }
509
510    /**
511     * Decides whether the number of an AST is in the ignore list of this
512     * check.
513     *
514     * @param ast the AST to check
515     * @return true if the number of ast is in the ignore list of this check.
516     */
517    private boolean isInIgnoreList(DetailAST ast) {
518        double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
519        final DetailAST parent = ast.getParent();
520        if (parent.getType() == TokenTypes.UNARY_MINUS) {
521            value = -1 * value;
522        }
523        return Arrays.binarySearch(ignoreNumbers, value) >= 0;
524    }
525
526    /**
527     * Determines whether or not the given AST is field declaration.
528     *
529     * @param ast AST from which to search for an enclosing field declaration
530     *
531     * @return {@code true} if {@code ast} is in the scope of field declaration
532     */
533    private static boolean isFieldDeclaration(DetailAST ast) {
534        DetailAST varDefAST = ast;
535        while (varDefAST != null
536                && varDefAST.getType() != TokenTypes.VARIABLE_DEF) {
537            varDefAST = varDefAST.getParent();
538        }
539
540        // contains variable declaration
541        // and it is directly inside class or record declaration
542        return varDefAST != null
543                && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
544                || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF);
545    }
546
547    /**
548     * Setter to specify tokens that are allowed in the AST path from the
549     * number literal to the enclosing constant definition.
550     *
551     * @param tokens The string representation of the tokens interested in
552     */
553    public void setConstantWaiverParentToken(String... tokens) {
554        constantWaiverParentToken = new int[tokens.length];
555        for (int i = 0; i < tokens.length; i++) {
556            constantWaiverParentToken[i] = TokenUtil.getTokenId(tokens[i]);
557        }
558        Arrays.sort(constantWaiverParentToken);
559    }
560
561    /**
562     * Setter to specify non-magic numbers.
563     *
564     * @param list list of numbers to ignore.
565     */
566    public void setIgnoreNumbers(double... list) {
567        if (list.length == 0) {
568            ignoreNumbers = CommonUtil.EMPTY_DOUBLE_ARRAY;
569        }
570        else {
571            ignoreNumbers = new double[list.length];
572            System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
573            Arrays.sort(ignoreNumbers);
574        }
575    }
576
577    /**
578     * Setter to ignore magic numbers in hashCode methods.
579     *
580     * @param ignoreHashCodeMethod decide whether to ignore
581     *     hash code methods
582     */
583    public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
584        this.ignoreHashCodeMethod = ignoreHashCodeMethod;
585    }
586
587    /**
588     * Setter to ignore magic numbers in annotation declarations.
589     *
590     * @param ignoreAnnotation decide whether to ignore annotations
591     */
592    public void setIgnoreAnnotation(boolean ignoreAnnotation) {
593        this.ignoreAnnotation = ignoreAnnotation;
594    }
595
596    /**
597     * Setter to ignore magic numbers in field declarations.
598     *
599     * @param ignoreFieldDeclaration decide whether to ignore magic numbers
600     *     in field declaration
601     */
602    public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
603        this.ignoreFieldDeclaration = ignoreFieldDeclaration;
604    }
605
606    /**
607     * Setter to ignore magic numbers in annotation elements defaults.
608     *
609     * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
610     */
611    public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
612        this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
613    }
614
615    /**
616     * Determines if the given AST node has a parent node with given token type code.
617     *
618     * @param ast the AST from which to search for annotations
619     * @param type the type code of parent token
620     *
621     * @return {@code true} if the AST node has a parent with given token type.
622     */
623    private static boolean isChildOf(DetailAST ast, int type) {
624        boolean result = false;
625        DetailAST node = ast;
626        do {
627            if (node.getType() == type) {
628                result = true;
629                break;
630            }
631            node = node.getParent();
632        } while (node != null);
633
634        return result;
635    }
636
637}