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