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