View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.Arrays;
23  import java.util.BitSet;
24  
25  import com.puppycrawl.tools.checkstyle.PropertyType;
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
34  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
35  
36  /**
37   * <div>
38   * Checks that there are no
39   * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
40   * &quot;magic numbers&quot;</a> where a magic
41   * number is a numeric literal that is not defined as a constant.
42   * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
43   * </div>
44   *
45   * <p>Constant definition is any variable/field that has 'final' modifier.
46   * It is fine to have one constant defining multiple numeric literals within one expression:
47   * </p>
48   * <pre>
49   * static final int SECONDS_PER_DAY = 24 * 60 * 60;
50   * static final double SPECIAL_RATIO = 4.0 / 3.0;
51   * static final double SPECIAL_SUM = 1 + Math.E;
52   * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
53   * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
54   * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);
55   * </pre>
56   * <ul>
57   * <li>
58   * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path
59   * from the number literal to the enclosing constant definition.
60   * Type is {@code java.lang.String[]}.
61   * Validation type is {@code tokenTypesSet}.
62   * Default value is
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
64   * ARRAY_INIT</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
66   * ASSIGN</a>,
67   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
68   * BAND</a>,
69   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BNOT">
70   * BNOT</a>,
71   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
72   * BOR</a>,
73   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
74   * BSR</a>,
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
76   * BXOR</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
78   * COLON</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
80   * DIV</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST">
82   * ELIST</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
84   * EQUAL</a>,
85   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
86   * EXPR</a>,
87   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
88   * GE</a>,
89   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
90   * GT</a>,
91   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
92   * LE</a>,
93   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
94   * LITERAL_NEW</a>,
95   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
96   * LT</a>,
97   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
98   * METHOD_CALL</a>,
99   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
100  * MINUS</a>,
101  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
102  * MOD</a>,
103  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
104  * NOT_EQUAL</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#QUESTION">
108  * QUESTION</a>,
109  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
110  * SL</a>,
111  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
112  * SR</a>,
113  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
114  * STAR</a>,
115  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
116  * TYPECAST</a>,
117  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
118  * UNARY_MINUS</a>,
119  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
120  * UNARY_PLUS</a>.
121  * </li>
122  * <li>
123  * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations.
124  * Type is {@code boolean}.
125  * Default value is {@code false}.
126  * </li>
127  * <li>
128  * Property {@code ignoreAnnotationElementDefaults} -
129  * Ignore magic numbers in annotation elements defaults.
130  * Type is {@code boolean}.
131  * Default value is {@code true}.
132  * </li>
133  * <li>
134  * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations.
135  * Type is {@code boolean}.
136  * Default value is {@code false}.
137  * </li>
138  * <li>
139  * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods.
140  * Type is {@code boolean}.
141  * Default value is {@code false}.
142  * </li>
143  * <li>
144  * Property {@code ignoreNumbers} - Specify non-magic numbers.
145  * Type is {@code double[]}.
146  * Default value is {@code -1, 0, 1, 2}.
147  * </li>
148  * <li>
149  * Property {@code tokens} - tokens to check
150  * Type is {@code java.lang.String[]}.
151  * Validation type is {@code tokenSet}.
152  * Default value is:
153  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
154  * NUM_DOUBLE</a>,
155  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
156  * NUM_FLOAT</a>,
157  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
158  * NUM_INT</a>,
159  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
160  * NUM_LONG</a>.
161  * </li>
162  * </ul>
163  *
164  * <p>
165  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
166  * </p>
167  *
168  * <p>
169  * Violation Message Keys:
170  * </p>
171  * <ul>
172  * <li>
173  * {@code magic.number}
174  * </li>
175  * </ul>
176  *
177  * @since 3.1
178  */
179 @StatelessCheck
180 public class MagicNumberCheck extends AbstractCheck {
181 
182     /**
183      * A key is pointing to the warning message text in "messages.properties"
184      * file.
185      */
186     public static final String MSG_KEY = "magic.number";
187 
188     /**
189      * Specify tokens that are allowed in the AST path from the
190      * number literal to the enclosing constant definition.
191      */
192     @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
193     private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
194         TokenTypes.ASSIGN,
195         TokenTypes.ARRAY_INIT,
196         TokenTypes.EXPR,
197         TokenTypes.UNARY_PLUS,
198         TokenTypes.UNARY_MINUS,
199         TokenTypes.TYPECAST,
200         TokenTypes.ELIST,
201         TokenTypes.LITERAL_NEW,
202         TokenTypes.METHOD_CALL,
203         TokenTypes.STAR,
204         TokenTypes.DIV,
205         TokenTypes.PLUS,
206         TokenTypes.MINUS,
207         TokenTypes.QUESTION,
208         TokenTypes.COLON,
209         TokenTypes.EQUAL,
210         TokenTypes.NOT_EQUAL,
211         TokenTypes.MOD,
212         TokenTypes.SR,
213         TokenTypes.BSR,
214         TokenTypes.GE,
215         TokenTypes.GT,
216         TokenTypes.SL,
217         TokenTypes.LE,
218         TokenTypes.LT,
219         TokenTypes.BXOR,
220         TokenTypes.BOR,
221         TokenTypes.BNOT,
222         TokenTypes.BAND
223     );
224 
225     /** Specify non-magic numbers. */
226     private double[] ignoreNumbers = {-1, 0, 1, 2};
227 
228     /** Ignore magic numbers in hashCode methods. */
229     private boolean ignoreHashCodeMethod;
230 
231     /** Ignore magic numbers in annotation declarations. */
232     private boolean ignoreAnnotation;
233 
234     /** Ignore magic numbers in field declarations. */
235     private boolean ignoreFieldDeclaration;
236 
237     /** Ignore magic numbers in annotation elements defaults. */
238     private boolean ignoreAnnotationElementDefaults = true;
239 
240     @Override
241     public int[] getDefaultTokens() {
242         return getAcceptableTokens();
243     }
244 
245     @Override
246     public int[] getAcceptableTokens() {
247         return new int[] {
248             TokenTypes.NUM_DOUBLE,
249             TokenTypes.NUM_FLOAT,
250             TokenTypes.NUM_INT,
251             TokenTypes.NUM_LONG,
252         };
253     }
254 
255     @Override
256     public int[] getRequiredTokens() {
257         return CommonUtil.EMPTY_INT_ARRAY;
258     }
259 
260     @Override
261     public void visitToken(DetailAST ast) {
262         if (shouldTestAnnotationArgs(ast)
263                 && shouldTestAnnotationDefaults(ast)
264                 && !isInIgnoreList(ast)
265                 && shouldCheckHashCodeMethod(ast)
266                 && shouldCheckFieldDeclaration(ast)) {
267             final DetailAST constantDefAST = findContainingConstantDef(ast);
268             if (isMagicNumberExists(ast, constantDefAST)) {
269                 reportMagicNumber(ast);
270             }
271         }
272     }
273 
274     /**
275      * Checks if ast is annotation argument and should be checked.
276      *
277      * @param ast token to check
278      * @return true if element is skipped, false otherwise
279      */
280     private boolean shouldTestAnnotationArgs(DetailAST ast) {
281         return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
282     }
283 
284     /**
285      * Checks if ast is annotation element default value and should be checked.
286      *
287      * @param ast token to check
288      * @return true if element is skipped, false otherwise
289      */
290     private boolean shouldTestAnnotationDefaults(DetailAST ast) {
291         return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
292     }
293 
294     /**
295      * Checks if the given AST node is a HashCode Method and should be checked.
296      *
297      * @param ast the AST node to check
298      * @return true if element should be checked, false otherwise
299      */
300     private boolean shouldCheckHashCodeMethod(DetailAST ast) {
301         return !ignoreHashCodeMethod || !isInHashCodeMethod(ast);
302     }
303 
304     /**
305      * Checks if the given AST node is a field declaration and should be checked.
306      *
307      * @param ast the AST node to check
308      * @return true if element should be checked, false otherwise
309      */
310     private boolean shouldCheckFieldDeclaration(DetailAST ast) {
311         return !ignoreFieldDeclaration || !isFieldDeclaration(ast);
312     }
313 
314     /**
315      * Is magic number somewhere at ast tree.
316      *
317      * @param ast ast token
318      * @param constantDefAST constant ast
319      * @return true if magic number is present
320      */
321     private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
322         boolean found = false;
323         DetailAST astNode = ast.getParent();
324         while (astNode != constantDefAST) {
325             final int type = astNode.getType();
326 
327             if (!constantWaiverParentToken.get(type)) {
328                 found = true;
329                 break;
330             }
331 
332             astNode = astNode.getParent();
333         }
334         return found;
335     }
336 
337     /**
338      * Finds the constant definition that contains aAST.
339      *
340      * @param ast the AST
341      * @return the constant def or null if ast is not contained in a constant definition.
342      */
343     private static DetailAST findContainingConstantDef(DetailAST ast) {
344         DetailAST varDefAST = ast;
345         while (varDefAST != null
346                 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
347                 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
348             varDefAST = varDefAST.getParent();
349         }
350         DetailAST constantDef = null;
351 
352         // no containing variable definition?
353         if (varDefAST != null) {
354             // implicit constant?
355             if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
356                     || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
357                 constantDef = varDefAST;
358             }
359             else {
360                 // explicit constant
361                 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
362 
363                 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
364                     constantDef = varDefAST;
365                 }
366             }
367         }
368         return constantDef;
369     }
370 
371     /**
372      * Reports aAST as a magic number, includes unary operators as needed.
373      *
374      * @param ast the AST node that contains the number to report
375      */
376     private void reportMagicNumber(DetailAST ast) {
377         String text = ast.getText();
378         final DetailAST parent = ast.getParent();
379         DetailAST reportAST = ast;
380         if (parent.getType() == TokenTypes.UNARY_MINUS) {
381             reportAST = parent;
382             text = "-" + text;
383         }
384         else if (parent.getType() == TokenTypes.UNARY_PLUS) {
385             reportAST = parent;
386             text = "+" + text;
387         }
388         log(reportAST,
389                 MSG_KEY,
390                 text);
391     }
392 
393     /**
394      * Determines whether or not the given AST is in a valid hash code method.
395      * A valid hash code method is considered to be a method of the signature
396      * {@code public int hashCode()}.
397      *
398      * @param ast the AST from which to search for an enclosing hash code
399      *     method definition
400      *
401      * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
402      */
403     private static boolean isInHashCodeMethod(DetailAST ast) {
404         // find the method definition AST
405         DetailAST currentAST = ast;
406         while (currentAST != null
407                 && currentAST.getType() != TokenTypes.METHOD_DEF) {
408             currentAST = currentAST.getParent();
409         }
410         final DetailAST methodDefAST = currentAST;
411         boolean inHashCodeMethod = false;
412 
413         if (methodDefAST != null) {
414             // Check for 'hashCode' name.
415             final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
416 
417             if ("hashCode".equals(identAST.getText())) {
418                 // Check for no arguments.
419                 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
420                 // we are in a 'public int hashCode()' method! The compiler will ensure
421                 // the method returns an 'int' and is public.
422                 inHashCodeMethod = !paramAST.hasChildren();
423             }
424         }
425         return inHashCodeMethod;
426     }
427 
428     /**
429      * Decides whether the number of an AST is in the ignore list of this
430      * check.
431      *
432      * @param ast the AST to check
433      * @return true if the number of ast is in the ignore list of this check.
434      */
435     private boolean isInIgnoreList(DetailAST ast) {
436         double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
437         final DetailAST parent = ast.getParent();
438         if (parent.getType() == TokenTypes.UNARY_MINUS) {
439             value = -1 * value;
440         }
441         return Arrays.binarySearch(ignoreNumbers, value) >= 0;
442     }
443 
444     /**
445      * Determines whether or not the given AST is field declaration.
446      *
447      * @param ast AST from which to search for an enclosing field declaration
448      *
449      * @return {@code true} if {@code ast} is in the scope of field declaration
450      */
451     private static boolean isFieldDeclaration(DetailAST ast) {
452         DetailAST varDefAST = null;
453         DetailAST node = ast;
454         while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
455             if (node.getType() == TokenTypes.VARIABLE_DEF) {
456                 varDefAST = node;
457                 break;
458             }
459             node = node.getParent();
460         }
461 
462         // contains variable declaration
463         // and it is directly inside class or record declaration
464         return varDefAST != null
465                 && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
466                 || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF
467                 || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW);
468 
469     }
470 
471     /**
472      * Setter to specify tokens that are allowed in the AST path from the
473      * number literal to the enclosing constant definition.
474      *
475      * @param tokens The string representation of the tokens interested in
476      * @since 6.11
477      */
478     public void setConstantWaiverParentToken(String... tokens) {
479         constantWaiverParentToken = TokenUtil.asBitSet(tokens);
480     }
481 
482     /**
483      * Setter to specify non-magic numbers.
484      *
485      * @param list numbers to ignore.
486      * @since 3.1
487      */
488     public void setIgnoreNumbers(double... list) {
489         ignoreNumbers = new double[list.length];
490         System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
491         Arrays.sort(ignoreNumbers);
492     }
493 
494     /**
495      * Setter to ignore magic numbers in hashCode methods.
496      *
497      * @param ignoreHashCodeMethod decide whether to ignore
498      *     hash code methods
499      * @since 5.3
500      */
501     public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
502         this.ignoreHashCodeMethod = ignoreHashCodeMethod;
503     }
504 
505     /**
506      * Setter to ignore magic numbers in annotation declarations.
507      *
508      * @param ignoreAnnotation decide whether to ignore annotations
509      * @since 5.4
510      */
511     public void setIgnoreAnnotation(boolean ignoreAnnotation) {
512         this.ignoreAnnotation = ignoreAnnotation;
513     }
514 
515     /**
516      * Setter to ignore magic numbers in field declarations.
517      *
518      * @param ignoreFieldDeclaration decide whether to ignore magic numbers
519      *     in field declaration
520      * @since 6.6
521      */
522     public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
523         this.ignoreFieldDeclaration = ignoreFieldDeclaration;
524     }
525 
526     /**
527      * Setter to ignore magic numbers in annotation elements defaults.
528      *
529      * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
530      * @since 8.23
531      */
532     public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
533         this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
534     }
535 
536     /**
537      * Determines if the given AST node has a parent node with given token type code.
538      *
539      * @param ast the AST from which to search for annotations
540      * @param type the type code of parent token
541      *
542      * @return {@code true} if the AST node has a parent with given token type.
543      */
544     private static boolean isChildOf(DetailAST ast, int type) {
545         boolean result = false;
546         DetailAST node = ast;
547         do {
548             if (node.getType() == type) {
549                 result = true;
550                 break;
551             }
552             node = node.getParent();
553         } while (node != null);
554 
555         return result;
556     }
557 
558 }