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