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