001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 constantWaiverParentToken} - Specify tokens that are allowed in the AST path
059 * from the number literal to the enclosing constant definition.
060 * Type is {@code java.lang.String[]}.
061 * Validation type is {@code tokenTypesSet}.
062 * Default value is
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
064 * ARRAY_INIT</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
066 * ASSIGN</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
068 * DIV</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST">
070 * ELIST</a>,
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
072 * EXPR</a>,
073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
074 * LITERAL_NEW</a>,
075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
076 * METHOD_CALL</a>,
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
078 * MINUS</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
080 * PLUS</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
082 * STAR</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST">
084 * TYPECAST</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
086 * UNARY_MINUS</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
088 * UNARY_PLUS</a>.
089 * </li>
090 * <li>
091 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations.
092 * Type is {@code boolean}.
093 * Default value is {@code false}.
094 * </li>
095 * <li>
096 * Property {@code ignoreAnnotationElementDefaults} -
097 * Ignore magic numbers in annotation elements defaults.
098 * Type is {@code boolean}.
099 * Default value is {@code true}.
100 * </li>
101 * <li>
102 * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations.
103 * Type is {@code boolean}.
104 * Default value is {@code false}.
105 * </li>
106 * <li>
107 * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods.
108 * Type is {@code boolean}.
109 * Default value is {@code false}.
110 * </li>
111 * <li>
112 * Property {@code ignoreNumbers} - Specify non-magic numbers.
113 * Type is {@code double[]}.
114 * Default value is {@code -1, 0, 1, 2}.
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 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
133 * </p>
134 * <p>
135 * Violation Message Keys:
136 * </p>
137 * <ul>
138 * <li>
139 * {@code magic.number}
140 * </li>
141 * </ul>
142 *
143 * @since 3.1
144 */
145@StatelessCheck
146public class MagicNumberCheck extends AbstractCheck {
147
148    /**
149     * A key is pointing to the warning message text in "messages.properties"
150     * file.
151     */
152    public static final String MSG_KEY = "magic.number";
153
154    /**
155     * Specify tokens that are allowed in the AST path from the
156     * number literal to the enclosing constant definition.
157     */
158    @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
159    private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
160        TokenTypes.ASSIGN,
161        TokenTypes.ARRAY_INIT,
162        TokenTypes.EXPR,
163        TokenTypes.UNARY_PLUS,
164        TokenTypes.UNARY_MINUS,
165        TokenTypes.TYPECAST,
166        TokenTypes.ELIST,
167        TokenTypes.LITERAL_NEW,
168        TokenTypes.METHOD_CALL,
169        TokenTypes.STAR,
170        TokenTypes.DIV,
171        TokenTypes.PLUS,
172        TokenTypes.MINUS
173    );
174
175    /** Specify non-magic numbers. */
176    private double[] ignoreNumbers = {-1, 0, 1, 2};
177
178    /** Ignore magic numbers in hashCode methods. */
179    private boolean ignoreHashCodeMethod;
180
181    /** Ignore magic numbers in annotation declarations. */
182    private boolean ignoreAnnotation;
183
184    /** Ignore magic numbers in field declarations. */
185    private boolean ignoreFieldDeclaration;
186
187    /** Ignore magic numbers in annotation elements defaults. */
188    private boolean ignoreAnnotationElementDefaults = true;
189
190    @Override
191    public int[] getDefaultTokens() {
192        return getAcceptableTokens();
193    }
194
195    @Override
196    public int[] getAcceptableTokens() {
197        return new int[] {
198            TokenTypes.NUM_DOUBLE,
199            TokenTypes.NUM_FLOAT,
200            TokenTypes.NUM_INT,
201            TokenTypes.NUM_LONG,
202        };
203    }
204
205    @Override
206    public int[] getRequiredTokens() {
207        return CommonUtil.EMPTY_INT_ARRAY;
208    }
209
210    @Override
211    public void visitToken(DetailAST ast) {
212        if (shouldTestAnnotationArgs(ast)
213                && shouldTestAnnotationDefaults(ast)
214                && !isInIgnoreList(ast)
215                && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
216            final DetailAST constantDefAST = findContainingConstantDef(ast);
217
218            if (constantDefAST == null) {
219                if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
220                    reportMagicNumber(ast);
221                }
222            }
223            else {
224                final boolean found = isMagicNumberExists(ast, constantDefAST);
225                if (found) {
226                    reportMagicNumber(ast);
227                }
228            }
229        }
230    }
231
232    /**
233     * Checks if ast is annotation argument and should be checked.
234     *
235     * @param ast token to check
236     * @return true if element is skipped, false otherwise
237     */
238    private boolean shouldTestAnnotationArgs(DetailAST ast) {
239        return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
240    }
241
242    /**
243     * Checks if ast is annotation element default value and should be checked.
244     *
245     * @param ast token to check
246     * @return true if element is skipped, false otherwise
247     */
248    private boolean shouldTestAnnotationDefaults(DetailAST ast) {
249        return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
250    }
251
252    /**
253     * Is magic number somewhere at ast tree.
254     *
255     * @param ast ast token
256     * @param constantDefAST constant ast
257     * @return true if magic number is present
258     */
259    private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
260        boolean found = false;
261        DetailAST astNode = ast.getParent();
262        while (astNode != constantDefAST) {
263            final int type = astNode.getType();
264            if (!constantWaiverParentToken.get(type)) {
265                found = true;
266                break;
267            }
268            astNode = astNode.getParent();
269        }
270        return found;
271    }
272
273    /**
274     * Finds the constant definition that contains aAST.
275     *
276     * @param ast the AST
277     * @return the constant def or null if ast is not contained in a constant definition.
278     */
279    private static DetailAST findContainingConstantDef(DetailAST ast) {
280        DetailAST varDefAST = ast;
281        while (varDefAST != null
282                && varDefAST.getType() != TokenTypes.VARIABLE_DEF
283                && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
284            varDefAST = varDefAST.getParent();
285        }
286        DetailAST constantDef = null;
287
288        // no containing variable definition?
289        if (varDefAST != null) {
290            // implicit constant?
291            if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
292                    || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
293                constantDef = varDefAST;
294            }
295            else {
296                // explicit constant
297                final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
298
299                if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
300                    constantDef = varDefAST;
301                }
302            }
303        }
304        return constantDef;
305    }
306
307    /**
308     * Reports aAST as a magic number, includes unary operators as needed.
309     *
310     * @param ast the AST node that contains the number to report
311     */
312    private void reportMagicNumber(DetailAST ast) {
313        String text = ast.getText();
314        final DetailAST parent = ast.getParent();
315        DetailAST reportAST = ast;
316        if (parent.getType() == TokenTypes.UNARY_MINUS) {
317            reportAST = parent;
318            text = "-" + text;
319        }
320        else if (parent.getType() == TokenTypes.UNARY_PLUS) {
321            reportAST = parent;
322            text = "+" + text;
323        }
324        log(reportAST,
325                MSG_KEY,
326                text);
327    }
328
329    /**
330     * Determines whether or not the given AST is in a valid hash code method.
331     * A valid hash code method is considered to be a method of the signature
332     * {@code public int hashCode()}.
333     *
334     * @param ast the AST from which to search for an enclosing hash code
335     *     method definition
336     *
337     * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
338     */
339    private static boolean isInHashCodeMethod(DetailAST ast) {
340        // find the method definition AST
341        DetailAST currentAST = ast;
342        while (currentAST != null
343                && currentAST.getType() != TokenTypes.METHOD_DEF) {
344            currentAST = currentAST.getParent();
345        }
346        final DetailAST methodDefAST = currentAST;
347        boolean inHashCodeMethod = false;
348
349        if (methodDefAST != null) {
350            // Check for 'hashCode' name.
351            final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
352
353            if ("hashCode".equals(identAST.getText())) {
354                // Check for no arguments.
355                final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
356                // we are in a 'public int hashCode()' method! The compiler will ensure
357                // the method returns an 'int' and is public.
358                inHashCodeMethod = !paramAST.hasChildren();
359            }
360        }
361        return inHashCodeMethod;
362    }
363
364    /**
365     * Decides whether the number of an AST is in the ignore list of this
366     * check.
367     *
368     * @param ast the AST to check
369     * @return true if the number of ast is in the ignore list of this check.
370     */
371    private boolean isInIgnoreList(DetailAST ast) {
372        double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
373        final DetailAST parent = ast.getParent();
374        if (parent.getType() == TokenTypes.UNARY_MINUS) {
375            value = -1 * value;
376        }
377        return Arrays.binarySearch(ignoreNumbers, value) >= 0;
378    }
379
380    /**
381     * Determines whether or not the given AST is field declaration.
382     *
383     * @param ast AST from which to search for an enclosing field declaration
384     *
385     * @return {@code true} if {@code ast} is in the scope of field declaration
386     */
387    private static boolean isFieldDeclaration(DetailAST ast) {
388        DetailAST varDefAST = null;
389        DetailAST node = ast;
390        while (node.getType() != TokenTypes.OBJBLOCK) {
391            if (node.getType() == TokenTypes.VARIABLE_DEF) {
392                varDefAST = node;
393                break;
394            }
395            node = node.getParent();
396        }
397
398        // contains variable declaration
399        // and it is directly inside class or record declaration
400        return varDefAST != null
401                && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
402                || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF
403                || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW);
404    }
405
406    /**
407     * Setter to specify tokens that are allowed in the AST path from the
408     * number literal to the enclosing constant definition.
409     *
410     * @param tokens The string representation of the tokens interested in
411     * @since 6.11
412     */
413    public void setConstantWaiverParentToken(String... tokens) {
414        constantWaiverParentToken = TokenUtil.asBitSet(tokens);
415    }
416
417    /**
418     * Setter to specify non-magic numbers.
419     *
420     * @param list numbers to ignore.
421     * @since 3.1
422     */
423    public void setIgnoreNumbers(double... list) {
424        ignoreNumbers = new double[list.length];
425        System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
426        Arrays.sort(ignoreNumbers);
427    }
428
429    /**
430     * Setter to ignore magic numbers in hashCode methods.
431     *
432     * @param ignoreHashCodeMethod decide whether to ignore
433     *     hash code methods
434     * @since 5.3
435     */
436    public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
437        this.ignoreHashCodeMethod = ignoreHashCodeMethod;
438    }
439
440    /**
441     * Setter to ignore magic numbers in annotation declarations.
442     *
443     * @param ignoreAnnotation decide whether to ignore annotations
444     * @since 5.4
445     */
446    public void setIgnoreAnnotation(boolean ignoreAnnotation) {
447        this.ignoreAnnotation = ignoreAnnotation;
448    }
449
450    /**
451     * Setter to ignore magic numbers in field declarations.
452     *
453     * @param ignoreFieldDeclaration decide whether to ignore magic numbers
454     *     in field declaration
455     * @since 6.6
456     */
457    public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
458        this.ignoreFieldDeclaration = ignoreFieldDeclaration;
459    }
460
461    /**
462     * Setter to ignore magic numbers in annotation elements defaults.
463     *
464     * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
465     * @since 8.23
466     */
467    public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
468        this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
469    }
470
471    /**
472     * Determines if the given AST node has a parent node with given token type code.
473     *
474     * @param ast the AST from which to search for annotations
475     * @param type the type code of parent token
476     *
477     * @return {@code true} if the AST node has a parent with given token type.
478     */
479    private static boolean isChildOf(DetailAST ast, int type) {
480        boolean result = false;
481        DetailAST node = ast;
482        do {
483            if (node.getType() == type) {
484                result = true;
485                break;
486            }
487            node = node.getParent();
488        } while (node != null);
489
490        return result;
491    }
492
493}