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 * "magic numbers"</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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
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 * </code></pre></div>
56 *
57 * @since 3.1
58 */
59 @StatelessCheck
60 public class MagicNumberCheck extends AbstractCheck {
61
62 /**
63 * A key is pointing to the warning message text in "messages.properties"
64 * file.
65 */
66 public static final String MSG_KEY = "magic.number";
67
68 /**
69 * Specify tokens that are allowed in the AST path from the
70 * number literal to the enclosing constant definition.
71 */
72 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
73 private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
74 TokenTypes.ASSIGN,
75 TokenTypes.ARRAY_INIT,
76 TokenTypes.EXPR,
77 TokenTypes.UNARY_PLUS,
78 TokenTypes.UNARY_MINUS,
79 TokenTypes.TYPECAST,
80 TokenTypes.ELIST,
81 TokenTypes.LITERAL_NEW,
82 TokenTypes.METHOD_CALL,
83 TokenTypes.STAR,
84 TokenTypes.DIV,
85 TokenTypes.PLUS,
86 TokenTypes.MINUS,
87 TokenTypes.QUESTION,
88 TokenTypes.COLON,
89 TokenTypes.EQUAL,
90 TokenTypes.NOT_EQUAL,
91 TokenTypes.MOD,
92 TokenTypes.SR,
93 TokenTypes.BSR,
94 TokenTypes.GE,
95 TokenTypes.GT,
96 TokenTypes.SL,
97 TokenTypes.LE,
98 TokenTypes.LT,
99 TokenTypes.BXOR,
100 TokenTypes.BOR,
101 TokenTypes.BNOT,
102 TokenTypes.BAND
103 );
104
105 /** Specify non-magic numbers. */
106 private double[] ignoreNumbers = {-1, 0, 1, 2};
107
108 /** Ignore magic numbers in hashCode methods. */
109 private boolean ignoreHashCodeMethod;
110
111 /** Ignore magic numbers in annotation declarations. */
112 private boolean ignoreAnnotation;
113
114 /** Ignore magic numbers in field declarations. */
115 private boolean ignoreFieldDeclaration;
116
117 /** Ignore magic numbers in annotation elements defaults. */
118 private boolean ignoreAnnotationElementDefaults = true;
119
120 @Override
121 public int[] getDefaultTokens() {
122 return getAcceptableTokens();
123 }
124
125 @Override
126 public int[] getAcceptableTokens() {
127 return new int[] {
128 TokenTypes.NUM_DOUBLE,
129 TokenTypes.NUM_FLOAT,
130 TokenTypes.NUM_INT,
131 TokenTypes.NUM_LONG,
132 };
133 }
134
135 @Override
136 public int[] getRequiredTokens() {
137 return CommonUtil.EMPTY_INT_ARRAY;
138 }
139
140 @Override
141 public void visitToken(DetailAST ast) {
142 if (shouldTestAnnotationArgs(ast)
143 && shouldTestAnnotationDefaults(ast)
144 && !isInIgnoreList(ast)
145 && shouldCheckHashCodeMethod(ast)
146 && shouldCheckFieldDeclaration(ast)) {
147 final DetailAST constantDefAST = findContainingConstantDef(ast);
148 if (isMagicNumberExists(ast, constantDefAST)) {
149 reportMagicNumber(ast);
150 }
151 }
152 }
153
154 /**
155 * Checks if ast is annotation argument and should be checked.
156 *
157 * @param ast token to check
158 * @return true if element is skipped, false otherwise
159 */
160 private boolean shouldTestAnnotationArgs(DetailAST ast) {
161 return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
162 }
163
164 /**
165 * Checks if ast is annotation element default value and should be checked.
166 *
167 * @param ast token to check
168 * @return true if element is skipped, false otherwise
169 */
170 private boolean shouldTestAnnotationDefaults(DetailAST ast) {
171 return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
172 }
173
174 /**
175 * Checks if the given AST node is a HashCode Method and should be checked.
176 *
177 * @param ast the AST node to check
178 * @return true if element should be checked, false otherwise
179 */
180 private boolean shouldCheckHashCodeMethod(DetailAST ast) {
181 return !ignoreHashCodeMethod || !isInHashCodeMethod(ast);
182 }
183
184 /**
185 * Checks if the given AST node is a field declaration and should be checked.
186 *
187 * @param ast the AST node to check
188 * @return true if element should be checked, false otherwise
189 */
190 private boolean shouldCheckFieldDeclaration(DetailAST ast) {
191 return !ignoreFieldDeclaration || !isFieldDeclaration(ast);
192 }
193
194 /**
195 * Is magic number somewhere at ast tree.
196 *
197 * @param ast ast token
198 * @param constantDefAST constant ast
199 * @return true if magic number is present
200 */
201 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
202 boolean found = false;
203 DetailAST astNode = ast.getParent();
204 while (astNode != constantDefAST) {
205 final int type = astNode.getType();
206
207 if (!constantWaiverParentToken.get(type)) {
208 found = true;
209 break;
210 }
211
212 astNode = astNode.getParent();
213 }
214 return found;
215 }
216
217 /**
218 * Finds the constant definition that contains aAST.
219 *
220 * @param ast the AST
221 * @return the constant def or null if ast is not contained in a constant definition.
222 */
223 private static DetailAST findContainingConstantDef(DetailAST ast) {
224 DetailAST varDefAST = ast;
225 while (varDefAST != null
226 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
227 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
228 varDefAST = varDefAST.getParent();
229 }
230 DetailAST constantDef = null;
231
232 // no containing variable definition?
233 if (varDefAST != null) {
234 // implicit constant?
235 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
236 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
237 constantDef = varDefAST;
238 }
239 else {
240 // explicit constant
241 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
242
243 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
244 constantDef = varDefAST;
245 }
246 }
247 }
248 return constantDef;
249 }
250
251 /**
252 * Reports aAST as a magic number, includes unary operators as needed.
253 *
254 * @param ast the AST node that contains the number to report
255 */
256 private void reportMagicNumber(DetailAST ast) {
257 String text = ast.getText();
258 final DetailAST parent = ast.getParent();
259 DetailAST reportAST = ast;
260 if (parent.getType() == TokenTypes.UNARY_MINUS) {
261 reportAST = parent;
262 text = "-" + text;
263 }
264 else if (parent.getType() == TokenTypes.UNARY_PLUS) {
265 reportAST = parent;
266 text = "+" + text;
267 }
268 log(reportAST,
269 MSG_KEY,
270 text);
271 }
272
273 /**
274 * Determines whether or not the given AST is in a valid hash code method.
275 * A valid hash code method is considered to be a method of the signature
276 * {@code public int hashCode()}.
277 *
278 * @param ast the AST from which to search for an enclosing hash code
279 * method definition
280 *
281 * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
282 */
283 private static boolean isInHashCodeMethod(DetailAST ast) {
284 // find the method definition AST
285 DetailAST currentAST = ast;
286 while (currentAST != null
287 && currentAST.getType() != TokenTypes.METHOD_DEF) {
288 currentAST = currentAST.getParent();
289 }
290 final DetailAST methodDefAST = currentAST;
291 boolean inHashCodeMethod = false;
292
293 if (methodDefAST != null) {
294 // Check for 'hashCode' name.
295 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
296
297 if ("hashCode".equals(identAST.getText())) {
298 // Check for no arguments.
299 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
300 // we are in a 'public int hashCode()' method! The compiler will ensure
301 // the method returns an 'int' and is public.
302 inHashCodeMethod = !paramAST.hasChildren();
303 }
304 }
305 return inHashCodeMethod;
306 }
307
308 /**
309 * Decides whether the number of an AST is in the ignore list of this
310 * check.
311 *
312 * @param ast the AST to check
313 * @return true if the number of ast is in the ignore list of this check.
314 */
315 private boolean isInIgnoreList(DetailAST ast) {
316 double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
317 final DetailAST parent = ast.getParent();
318 if (parent.getType() == TokenTypes.UNARY_MINUS) {
319 value = -1 * value;
320 }
321 return Arrays.binarySearch(ignoreNumbers, value) >= 0;
322 }
323
324 /**
325 * Determines whether or not the given AST is field declaration.
326 *
327 * @param ast AST from which to search for an enclosing field declaration
328 *
329 * @return {@code true} if {@code ast} is in the scope of field declaration
330 */
331 private static boolean isFieldDeclaration(DetailAST ast) {
332 DetailAST varDefAST = null;
333 DetailAST node = ast;
334 while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
335 if (node.getType() == TokenTypes.VARIABLE_DEF) {
336 varDefAST = node;
337 break;
338 }
339 node = node.getParent();
340 }
341
342 // contains variable declaration
343 // and it is directly inside class or record declaration
344 return varDefAST != null
345 && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
346 || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF
347 || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW);
348
349 }
350
351 /**
352 * Setter to specify tokens that are allowed in the AST path from the
353 * number literal to the enclosing constant definition.
354 *
355 * @param tokens The string representation of the tokens interested in
356 * @since 6.11
357 */
358 public void setConstantWaiverParentToken(String... tokens) {
359 constantWaiverParentToken = TokenUtil.asBitSet(tokens);
360 }
361
362 /**
363 * Setter to specify non-magic numbers.
364 *
365 * @param list numbers to ignore.
366 * @since 3.1
367 */
368 public void setIgnoreNumbers(double... list) {
369 ignoreNumbers = new double[list.length];
370 System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
371 Arrays.sort(ignoreNumbers);
372 }
373
374 /**
375 * Setter to ignore magic numbers in hashCode methods.
376 *
377 * @param ignoreHashCodeMethod decide whether to ignore
378 * hash code methods
379 * @since 5.3
380 */
381 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
382 this.ignoreHashCodeMethod = ignoreHashCodeMethod;
383 }
384
385 /**
386 * Setter to ignore magic numbers in annotation declarations.
387 *
388 * @param ignoreAnnotation decide whether to ignore annotations
389 * @since 5.4
390 */
391 public void setIgnoreAnnotation(boolean ignoreAnnotation) {
392 this.ignoreAnnotation = ignoreAnnotation;
393 }
394
395 /**
396 * Setter to ignore magic numbers in field declarations.
397 *
398 * @param ignoreFieldDeclaration decide whether to ignore magic numbers
399 * in field declaration
400 * @since 6.6
401 */
402 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
403 this.ignoreFieldDeclaration = ignoreFieldDeclaration;
404 }
405
406 /**
407 * Setter to ignore magic numbers in annotation elements defaults.
408 *
409 * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
410 * @since 8.23
411 */
412 public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
413 this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
414 }
415
416 /**
417 * Determines if the given AST node has a parent node with given token type code.
418 *
419 * @param ast the AST from which to search for annotations
420 * @param type the type code of parent token
421 *
422 * @return {@code true} if the AST node has a parent with given token type.
423 */
424 private static boolean isChildOf(DetailAST ast, int type) {
425 boolean result = false;
426 DetailAST node = ast;
427 do {
428 if (node.getType() == type) {
429 result = true;
430 break;
431 }
432 node = node.getParent();
433 } while (node != null);
434
435 return result;
436 }
437
438 }