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