1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
29  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
31  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32  
33  /**
34   * <p>
35   * Checks that there are no
36   * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
37   * &quot;magic numbers&quot;</a> where a magic
38   * number is a numeric literal that is not defined as a constant.
39   * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
40   * </p>
41   *
42   * <p>Constant definition is any variable/field that has 'final' modifier.
43   * It is fine to have one constant defining multiple numeric literals within one expression:
44   * </p>
45   * <pre>
46   * static final int SECONDS_PER_DAY = 24 * 60 * 60;
47   * static final double SPECIAL_RATIO = 4.0 / 3.0;
48   * static final double SPECIAL_SUM = 1 + Math.E;
49   * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
50   * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
51   * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);
52   * </pre>
53   * <ul>
54   * <li>
55   * Property {@code ignoreNumbers} - Specify non-magic numbers.
56   * Default value is {@code -1, 0, 1, 2}.
57   * </li>
58   * <li>
59   * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods.
60   * Default value is {@code false}.
61   * </li>
62   * <li>
63   * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations.
64   * Default value is {@code false}.
65   * </li>
66   * <li>
67   * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations.
68   * Default value is {@code false}.
69   * </li>
70   * <li>
71   * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path
72   * from the number literal to the enclosing constant definition.
73   * Default value is
74   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
75   * TYPECAST</a>,
76   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
77   * METHOD_CALL</a>,
78   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
79   * EXPR</a>,
80   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
81   * ARRAY_INIT</a>,
82   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
83   * UNARY_MINUS</a>,
84   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
85   * UNARY_PLUS</a>,
86   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST">
87   * ELIST</a>,
88   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
89   * STAR</a>,
90   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
91   * ASSIGN</a>,
92   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
93   * PLUS</a>,
94   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
95   * MINUS</a>,
96   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
97   * DIV</a>,
98   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
99   * LITERAL_NEW</a>.
100  * </li>
101  * <li>
102  * Property {@code tokens} - tokens to check
103  * Default value is:
104  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
105  * NUM_DOUBLE</a>,
106  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
107  * NUM_FLOAT</a>,
108  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
109  * NUM_INT</a>,
110  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
111  * NUM_LONG</a>.
112  * </li>
113  * </ul>
114  * <p>
115  * To configure the check with default configuration:
116  * </p>
117  * <pre>
118  * &lt;module name=&quot;MagicNumber&quot;/&gt;
119  * </pre>
120  * <p>
121  * results is following violations:
122  * </p>
123  * <pre>
124  * &#64;MyAnnotation(6) // violation
125  * class MyClass {
126  *   private field = 7; // violation
127  *
128  *   void foo() {
129  *     int i = i + 1; // no violation
130  *     int j = j + 8; // violation
131  *   }
132  * }
133  * </pre>
134  * <p>
135  * To configure the check so that it checks floating-point numbers
136  * that are not 0, 0.5, or 1:
137  * </p>
138  * <pre>
139  * &lt;module name=&quot;MagicNumber&quot;&gt;
140  *   &lt;property name=&quot;tokens&quot; value=&quot;NUM_DOUBLE, NUM_FLOAT&quot;/&gt;
141  *   &lt;property name=&quot;ignoreNumbers&quot; value=&quot;0, 0.5, 1&quot;/&gt;
142  *   &lt;property name=&quot;ignoreFieldDeclaration&quot; value=&quot;true&quot;/&gt;
143  *   &lt;property name=&quot;ignoreAnnotation&quot; value=&quot;true&quot;/&gt;
144  * &lt;/module&gt;
145  * </pre>
146  * <p>
147  * results is following violations:
148  * </p>
149  * <pre>
150  * &#64;MyAnnotation(6) // no violation
151  * class MyClass {
152  *   private field = 7; // no violation
153  *
154  *   void foo() {
155  *     int i = i + 1; // no violation
156  *     int j = j + 8; // violation
157  *   }
158  * }
159  * </pre>
160  * <p>
161  * Config example of constantWaiverParentToken option:
162  * </p>
163  * <pre>
164  * &lt;module name=&quot;MagicNumber&quot;&gt;
165  *   &lt;property name=&quot;constantWaiverParentToken&quot; value=&quot;ASSIGN,ARRAY_INIT,EXPR,
166  *   UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS &quot;/&gt;
167  * &lt;/module&gt;
168  * </pre>
169  * <p>
170  * result is following violation:
171  * </p>
172  * <pre>
173  * class TestMethodCall {
174  *   public void method2() {
175  *     final TestMethodCall dummyObject = new TestMethodCall(62);    //violation
176  *     final int a = 3;        // ok as waiver is ASSIGN
177  *     final int [] b = {4, 5} // ok as waiver is ARRAY_INIT
178  *     final int c = -3;       // ok as waiver is UNARY_MINUS
179  *     final int d = +4;       // ok as waiver is UNARY_PLUS
180  *     final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL
181  *     final int x = 3 * 4;    // violation
182  *     final int y = 3 / 4;    // ok as waiver is DIV
183  *     final int z = 3 + 4;    // ok as waiver is PLUS
184  *     final int w = 3 - 4;    // violation
185  *     final int x = (int)(3.4);    //ok as waiver is TYPECAST
186  *   }
187  * }
188  * </pre>
189  *
190  * @since 3.1
191  */
192 @StatelessCheck
193 public class MagicNumberCheck extends AbstractCheck {
194 
195     /**
196      * A key is pointing to the warning message text in "messages.properties"
197      * file.
198      */
199     public static final String MSG_KEY = "magic.number";
200 
201     /**
202      * Specify tokens that are allowed in the AST path from the
203      * number literal to the enclosing constant definition.
204      */
205     private int[] constantWaiverParentToken = {
206         TokenTypes.ASSIGN,
207         TokenTypes.ARRAY_INIT,
208         TokenTypes.EXPR,
209         TokenTypes.UNARY_PLUS,
210         TokenTypes.UNARY_MINUS,
211         TokenTypes.TYPECAST,
212         TokenTypes.ELIST,
213         TokenTypes.LITERAL_NEW,
214         TokenTypes.METHOD_CALL,
215         TokenTypes.STAR,
216         TokenTypes.DIV,
217         TokenTypes.PLUS,
218         TokenTypes.MINUS,
219     };
220 
221     /** Specify non-magic numbers. */
222     private double[] ignoreNumbers = {-1, 0, 1, 2};
223 
224     /** Ignore magic numbers in hashCode methods. */
225     private boolean ignoreHashCodeMethod;
226 
227     /** Ignore magic numbers in annotation declarations. */
228     private boolean ignoreAnnotation;
229 
230     /** Ignore magic numbers in field declarations. */
231     private boolean ignoreFieldDeclaration;
232 
233     /**
234      * Constructor for MagicNumber Check.
235      * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search.
236      */
237     public MagicNumberCheck() {
238         Arrays.sort(constantWaiverParentToken);
239     }
240 
241     @Override
242     public int[] getDefaultTokens() {
243         return getAcceptableTokens();
244     }
245 
246     @Override
247     public int[] getAcceptableTokens() {
248         return new int[] {
249             TokenTypes.NUM_DOUBLE,
250             TokenTypes.NUM_FLOAT,
251             TokenTypes.NUM_INT,
252             TokenTypes.NUM_LONG,
253         };
254     }
255 
256     @Override
257     public int[] getRequiredTokens() {
258         return CommonUtil.EMPTY_INT_ARRAY;
259     }
260 
261     @Override
262     public void visitToken(DetailAST ast) {
263         if ((!ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION))
264                 && !isInIgnoreList(ast)
265                 && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
266             final DetailAST constantDefAST = findContainingConstantDef(ast);
267 
268             if (constantDefAST == null) {
269                 if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
270                     reportMagicNumber(ast);
271                 }
272             }
273             else {
274                 final boolean found = isMagicNumberExists(ast, constantDefAST);
275                 if (found) {
276                     reportMagicNumber(ast);
277                 }
278             }
279         }
280     }
281 
282     /**
283      * Is magic number some where at ast tree.
284      * @param ast ast token
285      * @param constantDefAST constant ast
286      * @return true if magic number is present
287      */
288     private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
289         boolean found = false;
290         DetailAST astNode = ast.getParent();
291         while (astNode != constantDefAST) {
292             final int type = astNode.getType();
293             if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) {
294                 found = true;
295                 break;
296             }
297             astNode = astNode.getParent();
298         }
299         return found;
300     }
301 
302     /**
303      * Finds the constant definition that contains aAST.
304      * @param ast the AST
305      * @return the constant def or null if ast is not contained in a constant definition.
306      */
307     private static DetailAST findContainingConstantDef(DetailAST ast) {
308         DetailAST varDefAST = ast;
309         while (varDefAST != null
310                 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
311                 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
312             varDefAST = varDefAST.getParent();
313         }
314         DetailAST constantDef = null;
315 
316         // no containing variable definition?
317         if (varDefAST != null) {
318             // implicit constant?
319             if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
320                     || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
321                 constantDef = varDefAST;
322             }
323             else {
324                 // explicit constant
325                 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
326 
327                 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
328                     constantDef = varDefAST;
329                 }
330             }
331         }
332         return constantDef;
333     }
334 
335     /**
336      * Reports aAST as a magic number, includes unary operators as needed.
337      * @param ast the AST node that contains the number to report
338      */
339     private void reportMagicNumber(DetailAST ast) {
340         String text = ast.getText();
341         final DetailAST parent = ast.getParent();
342         DetailAST reportAST = ast;
343         if (parent.getType() == TokenTypes.UNARY_MINUS) {
344             reportAST = parent;
345             text = "-" + text;
346         }
347         else if (parent.getType() == TokenTypes.UNARY_PLUS) {
348             reportAST = parent;
349             text = "+" + text;
350         }
351         log(reportAST,
352                 MSG_KEY,
353                 text);
354     }
355 
356     /**
357      * Determines whether or not the given AST is in a valid hash code method.
358      * A valid hash code method is considered to be a method of the signature
359      * {@code public int hashCode()}.
360      *
361      * @param ast the AST from which to search for an enclosing hash code
362      *     method definition
363      *
364      * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
365      */
366     private static boolean isInHashCodeMethod(DetailAST ast) {
367         boolean inHashCodeMethod = false;
368 
369         // if not in a code block, can't be in hashCode()
370         if (ScopeUtil.isInCodeBlock(ast)) {
371             // find the method definition AST
372             DetailAST methodDefAST = ast.getParent();
373             while (methodDefAST != null
374                     && methodDefAST.getType() != TokenTypes.METHOD_DEF) {
375                 methodDefAST = methodDefAST.getParent();
376             }
377 
378             if (methodDefAST != null) {
379                 // Check for 'hashCode' name.
380                 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
381 
382                 if ("hashCode".equals(identAST.getText())) {
383                     // Check for no arguments.
384                     final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
385                     // we are in a 'public int hashCode()' method! The compiler will ensure
386                     // the method returns an 'int' and is public.
387                     inHashCodeMethod = paramAST.getChildCount() == 0;
388                 }
389             }
390         }
391         return inHashCodeMethod;
392     }
393 
394     /**
395      * Decides whether the number of an AST is in the ignore list of this
396      * check.
397      * @param ast the AST to check
398      * @return true if the number of ast is in the ignore list of this check.
399      */
400     private boolean isInIgnoreList(DetailAST ast) {
401         double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
402         final DetailAST parent = ast.getParent();
403         if (parent.getType() == TokenTypes.UNARY_MINUS) {
404             value = -1 * value;
405         }
406         return Arrays.binarySearch(ignoreNumbers, value) >= 0;
407     }
408 
409     /**
410      * Determines whether or not the given AST is field declaration.
411      *
412      * @param ast AST from which to search for an enclosing field declaration
413      *
414      * @return {@code true} if {@code ast} is in the scope of field declaration
415      */
416     private static boolean isFieldDeclaration(DetailAST ast) {
417         DetailAST varDefAST = ast;
418         while (varDefAST != null
419                 && varDefAST.getType() != TokenTypes.VARIABLE_DEF) {
420             varDefAST = varDefAST.getParent();
421         }
422 
423         // contains variable declaration
424         // and it is directly inside class declaration
425         return varDefAST != null
426                 && varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF;
427     }
428 
429     /**
430      * Setter to specify tokens that are allowed in the AST path from the
431      * number literal to the enclosing constant definition.
432      * @param tokens The string representation of the tokens interested in
433      */
434     public void setConstantWaiverParentToken(String... tokens) {
435         constantWaiverParentToken = new int[tokens.length];
436         for (int i = 0; i < tokens.length; i++) {
437             constantWaiverParentToken[i] = TokenUtil.getTokenId(tokens[i]);
438         }
439         Arrays.sort(constantWaiverParentToken);
440     }
441 
442     /**
443      * Setter to specify non-magic numbers.
444      * @param list list of numbers to ignore.
445      */
446     public void setIgnoreNumbers(double... list) {
447         if (list.length == 0) {
448             ignoreNumbers = CommonUtil.EMPTY_DOUBLE_ARRAY;
449         }
450         else {
451             ignoreNumbers = new double[list.length];
452             System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
453             Arrays.sort(ignoreNumbers);
454         }
455     }
456 
457     /**
458      * Setter to ignore magic numbers in hashCode methods.
459      * @param ignoreHashCodeMethod decide whether to ignore
460      *     hash code methods
461      */
462     public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
463         this.ignoreHashCodeMethod = ignoreHashCodeMethod;
464     }
465 
466     /**
467      * Setter to ignore magic numbers in annotation declarations.
468      * @param ignoreAnnotation decide whether to ignore annotations
469      */
470     public void setIgnoreAnnotation(boolean ignoreAnnotation) {
471         this.ignoreAnnotation = ignoreAnnotation;
472     }
473 
474     /**
475      * Setter to ignore magic numbers in field declarations.
476      * @param ignoreFieldDeclaration decide whether to ignore magic numbers
477      *     in field declaration
478      */
479     public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
480         this.ignoreFieldDeclaration = ignoreFieldDeclaration;
481     }
482 
483     /**
484      * Determines if the given AST node has a parent node with given token type code.
485      *
486      * @param ast the AST from which to search for annotations
487      * @param type the type code of parent token
488      *
489      * @return {@code true} if the AST node has a parent with given token type.
490      */
491     private static boolean isChildOf(DetailAST ast, int type) {
492         boolean result = false;
493         DetailAST node = ast;
494         do {
495             if (node.getType() == type) {
496                 result = true;
497                 break;
498             }
499             node = node.getParent();
500         } while (node != null);
501 
502         return result;
503     }
504 
505 }