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