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