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