1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 * {@code "magic number"}</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 * {@snippet lang="text" :
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 * }
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 /**
121 * Creates a new {@code MagicNumberCheck} instance.
122 */
123 public MagicNumberCheck() {
124 // no code by default
125 }
126
127 @Override
128 public int[] getDefaultTokens() {
129 return getAcceptableTokens();
130 }
131
132 @Override
133 public int[] getAcceptableTokens() {
134 return new int[] {
135 TokenTypes.NUM_DOUBLE,
136 TokenTypes.NUM_FLOAT,
137 TokenTypes.NUM_INT,
138 TokenTypes.NUM_LONG,
139 };
140 }
141
142 @Override
143 public int[] getRequiredTokens() {
144 return CommonUtil.EMPTY_INT_ARRAY;
145 }
146
147 @Override
148 public void visitToken(DetailAST ast) {
149 if (shouldTestAnnotationArgs(ast)
150 && shouldTestAnnotationDefaults(ast)
151 && !isInIgnoreList(ast)
152 && shouldCheckHashCodeMethod(ast)
153 && shouldCheckFieldDeclaration(ast)) {
154 final DetailAST constantDefAST = findContainingConstantDef(ast);
155 if (isMagicNumberExists(ast, constantDefAST)) {
156 reportMagicNumber(ast);
157 }
158 }
159 }
160
161 /**
162 * Checks if ast is annotation argument and should be checked.
163 *
164 * @param ast token to check
165 * @return true if element is skipped, false otherwise
166 */
167 private boolean shouldTestAnnotationArgs(DetailAST ast) {
168 return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
169 }
170
171 /**
172 * Checks if ast is annotation element default value and should be checked.
173 *
174 * @param ast token to check
175 * @return true if element is skipped, false otherwise
176 */
177 private boolean shouldTestAnnotationDefaults(DetailAST ast) {
178 return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
179 }
180
181 /**
182 * Checks if the given AST node is a HashCode Method and should be checked.
183 *
184 * @param ast the AST node to check
185 * @return true if element should be checked, false otherwise
186 */
187 private boolean shouldCheckHashCodeMethod(DetailAST ast) {
188 return !ignoreHashCodeMethod || !isInHashCodeMethod(ast);
189 }
190
191 /**
192 * Checks if the given AST node is a field declaration and should be checked.
193 *
194 * @param ast the AST node to check
195 * @return true if element should be checked, false otherwise
196 */
197 private boolean shouldCheckFieldDeclaration(DetailAST ast) {
198 return !ignoreFieldDeclaration || !isFieldDeclaration(ast);
199 }
200
201 /**
202 * Is magic number somewhere at ast tree.
203 *
204 * @param ast ast token
205 * @param constantDefAST constant ast
206 * @return true if magic number is present
207 */
208 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
209 boolean found = false;
210 DetailAST astNode = ast.getParent();
211 while (astNode != constantDefAST) {
212 final int type = astNode.getType();
213
214 if (!constantWaiverParentToken.get(type)) {
215 found = true;
216 break;
217 }
218
219 astNode = astNode.getParent();
220 }
221 return found;
222 }
223
224 /**
225 * Finds the constant definition that contains aAST.
226 *
227 * @param ast the AST
228 * @return the constant def or null if ast is not contained in a constant definition.
229 */
230 private static DetailAST findContainingConstantDef(DetailAST ast) {
231 DetailAST varDefAST = ast;
232 while (varDefAST != null
233 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
234 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
235 varDefAST = varDefAST.getParent();
236 }
237 DetailAST constantDef = null;
238
239 // no containing variable definition?
240 if (varDefAST != null) {
241 // implicit constant?
242 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
243 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
244 constantDef = varDefAST;
245 }
246 else {
247 // explicit constant
248 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
249
250 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
251 constantDef = varDefAST;
252 }
253 }
254 }
255 return constantDef;
256 }
257
258 /**
259 * Reports aAST as a magic number, includes unary operators as needed.
260 *
261 * @param ast the AST node that contains the number to report
262 */
263 private void reportMagicNumber(DetailAST ast) {
264 String text = ast.getText();
265 final DetailAST parent = ast.getParent();
266 DetailAST reportAST = ast;
267 if (parent.getType() == TokenTypes.UNARY_MINUS) {
268 reportAST = parent;
269 text = "-" + text;
270 }
271 else if (parent.getType() == TokenTypes.UNARY_PLUS) {
272 reportAST = parent;
273 text = "+" + text;
274 }
275 log(reportAST,
276 MSG_KEY,
277 text);
278 }
279
280 /**
281 * Determines whether or not the given AST is in a valid hash code method.
282 * A valid hash code method is considered to be a method of the signature
283 * {@code public int hashCode()}.
284 *
285 * @param ast the AST from which to search for an enclosing hash code
286 * method definition
287 *
288 * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
289 */
290 private static boolean isInHashCodeMethod(DetailAST ast) {
291 // find the method definition AST
292 DetailAST currentAST = ast;
293 while (currentAST != null
294 && currentAST.getType() != TokenTypes.METHOD_DEF) {
295 currentAST = currentAST.getParent();
296 }
297 final DetailAST methodDefAST = currentAST;
298 boolean inHashCodeMethod = false;
299
300 if (methodDefAST != null) {
301 // Check for 'hashCode' name.
302 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
303
304 if ("hashCode".equals(identAST.getText())) {
305 // Check for no arguments.
306 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
307 // we are in a 'public int hashCode()' method! The compiler will ensure
308 // the method returns an 'int' and is public.
309 inHashCodeMethod = !paramAST.hasChildren();
310 }
311 }
312 return inHashCodeMethod;
313 }
314
315 /**
316 * Decides whether the number of an AST is in the ignore list of this
317 * check.
318 *
319 * @param ast the AST to check
320 * @return true if the number of ast is in the ignore list of this check.
321 */
322 private boolean isInIgnoreList(DetailAST ast) {
323 double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
324 final DetailAST parent = ast.getParent();
325 if (parent.getType() == TokenTypes.UNARY_MINUS) {
326 value = -1 * value;
327 }
328 return Arrays.binarySearch(ignoreNumbers, value) >= 0;
329 }
330
331 /**
332 * Determines whether or not the given AST is field declaration.
333 *
334 * @param ast AST from which to search for an enclosing field declaration
335 *
336 * @return {@code true} if {@code ast} is in the scope of field declaration
337 */
338 private static boolean isFieldDeclaration(DetailAST ast) {
339 DetailAST varDefAST = null;
340 DetailAST node = ast;
341 while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
342 if (node.getType() == TokenTypes.VARIABLE_DEF) {
343 varDefAST = node;
344 break;
345 }
346 node = node.getParent();
347 }
348
349 boolean result = false;
350
351 if (varDefAST != null) {
352 final DetailAST parent = varDefAST.getParent();
353
354 if (parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
355 result = true;
356 }
357 else {
358 final DetailAST grandParent = parent.getParent();
359
360 result = grandParent.getType() == TokenTypes.CLASS_DEF
361 || grandParent.getType() == TokenTypes.RECORD_DEF
362 || grandParent.getType() == TokenTypes.LITERAL_NEW;
363 }
364 }
365
366 return result;
367 }
368
369 /**
370 * Setter to specify tokens that are allowed in the AST path from the
371 * number literal to the enclosing constant definition.
372 *
373 * @param tokens The string representation of the tokens interested in
374 * @since 6.11
375 */
376 public void setConstantWaiverParentToken(String... tokens) {
377 constantWaiverParentToken = TokenUtil.asBitSet(tokens);
378 }
379
380 /**
381 * Setter to specify non-magic numbers.
382 *
383 * @param list numbers to ignore.
384 * @since 3.1
385 */
386 public void setIgnoreNumbers(double... list) {
387 ignoreNumbers = new double[list.length];
388 System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
389 Arrays.sort(ignoreNumbers);
390 }
391
392 /**
393 * Setter to ignore magic numbers in hashCode methods.
394 *
395 * @param ignoreHashCodeMethod decide whether to ignore
396 * hash code methods
397 * @since 5.3
398 */
399 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
400 this.ignoreHashCodeMethod = ignoreHashCodeMethod;
401 }
402
403 /**
404 * Setter to ignore magic numbers in annotation declarations.
405 *
406 * @param ignoreAnnotation decide whether to ignore annotations
407 * @since 5.4
408 */
409 public void setIgnoreAnnotation(boolean ignoreAnnotation) {
410 this.ignoreAnnotation = ignoreAnnotation;
411 }
412
413 /**
414 * Setter to ignore magic numbers in field declarations.
415 *
416 * @param ignoreFieldDeclaration decide whether to ignore magic numbers
417 * in field declaration
418 * @since 6.6
419 */
420 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
421 this.ignoreFieldDeclaration = ignoreFieldDeclaration;
422 }
423
424 /**
425 * Setter to ignore magic numbers in annotation elements defaults.
426 *
427 * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults
428 * @since 8.23
429 */
430 public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
431 this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
432 }
433
434 /**
435 * Determines if the given AST node has a parent node with given token type code.
436 *
437 * @param ast the AST from which to search for annotations
438 * @param type the type code of parent token
439 *
440 * @return {@code true} if the AST node has a parent with given token type.
441 */
442 private static boolean isChildOf(DetailAST ast, int type) {
443 boolean result = false;
444 DetailAST node = ast;
445 do {
446 if (node.getType() == type) {
447 result = true;
448 break;
449 }
450 node = node.getParent();
451 } while (node != null);
452
453 return result;
454 }
455
456 }