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