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