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