1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.coding; 21 22 import java.util.Arrays; 23 24 import com.puppycrawl.tools.checkstyle.StatelessCheck; 25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 26 import com.puppycrawl.tools.checkstyle.api.DetailAST; 27 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 28 import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 30 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 32 33 /** 34 * <p> 35 * Checks that there are no 36 * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29"> 37 * "magic numbers"</a> where a magic 38 * number is a numeric literal that is not defined as a constant. 39 * By default, -1, 0, 1, and 2 are not considered to be magic numbers. 40 * </p> 41 * 42 * <p>Constant definition is any variable/field that has 'final' modifier. 43 * It is fine to have one constant defining multiple numeric literals within one expression: 44 * </p> 45 * <pre> 46 * static final int SECONDS_PER_DAY = 24 * 60 * 60; 47 * static final double SPECIAL_RATIO = 4.0 / 3.0; 48 * static final double SPECIAL_SUM = 1 + Math.E; 49 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI; 50 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3); 51 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42); 52 * </pre> 53 * <ul> 54 * <li> 55 * Property {@code ignoreNumbers} - Specify non-magic numbers. 56 * Default value is {@code -1, 0, 1, 2}. 57 * </li> 58 * <li> 59 * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods. 60 * Default value is {@code false}. 61 * </li> 62 * <li> 63 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations. 64 * Default value is {@code false}. 65 * </li> 66 * <li> 67 * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations. 68 * Default value is {@code false}. 69 * </li> 70 * <li> 71 * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path 72 * from the number literal to the enclosing constant definition. 73 * Default value is 74 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST"> 75 * TYPECAST</a>, 76 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 77 * METHOD_CALL</a>, 78 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 79 * EXPR</a>, 80 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT"> 81 * ARRAY_INIT</a>, 82 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS"> 83 * UNARY_MINUS</a>, 84 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS"> 85 * UNARY_PLUS</a>, 86 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST"> 87 * ELIST</a>, 88 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 89 * STAR</a>, 90 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN"> 91 * ASSIGN</a>, 92 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 93 * PLUS</a>, 94 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS"> 95 * MINUS</a>, 96 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV"> 97 * DIV</a>, 98 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 99 * LITERAL_NEW</a>. 100 * </li> 101 * <li> 102 * Property {@code tokens} - tokens to check 103 * Default value is: 104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE"> 105 * NUM_DOUBLE</a>, 106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT"> 107 * NUM_FLOAT</a>, 108 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT"> 109 * NUM_INT</a>, 110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG"> 111 * NUM_LONG</a>. 112 * </li> 113 * </ul> 114 * <p> 115 * To configure the check with default configuration: 116 * </p> 117 * <pre> 118 * <module name="MagicNumber"/> 119 * </pre> 120 * <p> 121 * results is following violations: 122 * </p> 123 * <pre> 124 * @MyAnnotation(6) // violation 125 * class MyClass { 126 * private field = 7; // violation 127 * 128 * void foo() { 129 * int i = i + 1; // no violation 130 * int j = j + 8; // violation 131 * } 132 * } 133 * </pre> 134 * <p> 135 * To configure the check so that it checks floating-point numbers 136 * that are not 0, 0.5, or 1: 137 * </p> 138 * <pre> 139 * <module name="MagicNumber"> 140 * <property name="tokens" value="NUM_DOUBLE, NUM_FLOAT"/> 141 * <property name="ignoreNumbers" value="0, 0.5, 1"/> 142 * <property name="ignoreFieldDeclaration" value="true"/> 143 * <property name="ignoreAnnotation" value="true"/> 144 * </module> 145 * </pre> 146 * <p> 147 * results is following violations: 148 * </p> 149 * <pre> 150 * @MyAnnotation(6) // no violation 151 * class MyClass { 152 * private field = 7; // no violation 153 * 154 * void foo() { 155 * int i = i + 1; // no violation 156 * int j = j + 8; // violation 157 * } 158 * } 159 * </pre> 160 * <p> 161 * Config example of constantWaiverParentToken option: 162 * </p> 163 * <pre> 164 * <module name="MagicNumber"> 165 * <property name="constantWaiverParentToken" value="ASSIGN,ARRAY_INIT,EXPR, 166 * UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS "/> 167 * </module> 168 * </pre> 169 * <p> 170 * result is following violation: 171 * </p> 172 * <pre> 173 * class TestMethodCall { 174 * public void method2() { 175 * final TestMethodCall dummyObject = new TestMethodCall(62); //violation 176 * final int a = 3; // ok as waiver is ASSIGN 177 * final int [] b = {4, 5} // ok as waiver is ARRAY_INIT 178 * final int c = -3; // ok as waiver is UNARY_MINUS 179 * final int d = +4; // ok as waiver is UNARY_PLUS 180 * final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL 181 * final int x = 3 * 4; // violation 182 * final int y = 3 / 4; // ok as waiver is DIV 183 * final int z = 3 + 4; // ok as waiver is PLUS 184 * final int w = 3 - 4; // violation 185 * final int x = (int)(3.4); //ok as waiver is TYPECAST 186 * } 187 * } 188 * </pre> 189 * 190 * @since 3.1 191 */ 192 @StatelessCheck 193 public class MagicNumberCheck extends AbstractCheck { 194 195 /** 196 * A key is pointing to the warning message text in "messages.properties" 197 * file. 198 */ 199 public static final String MSG_KEY = "magic.number"; 200 201 /** 202 * Specify tokens that are allowed in the AST path from the 203 * number literal to the enclosing constant definition. 204 */ 205 private int[] constantWaiverParentToken = { 206 TokenTypes.ASSIGN, 207 TokenTypes.ARRAY_INIT, 208 TokenTypes.EXPR, 209 TokenTypes.UNARY_PLUS, 210 TokenTypes.UNARY_MINUS, 211 TokenTypes.TYPECAST, 212 TokenTypes.ELIST, 213 TokenTypes.LITERAL_NEW, 214 TokenTypes.METHOD_CALL, 215 TokenTypes.STAR, 216 TokenTypes.DIV, 217 TokenTypes.PLUS, 218 TokenTypes.MINUS, 219 }; 220 221 /** Specify non-magic numbers. */ 222 private double[] ignoreNumbers = {-1, 0, 1, 2}; 223 224 /** Ignore magic numbers in hashCode methods. */ 225 private boolean ignoreHashCodeMethod; 226 227 /** Ignore magic numbers in annotation declarations. */ 228 private boolean ignoreAnnotation; 229 230 /** Ignore magic numbers in field declarations. */ 231 private boolean ignoreFieldDeclaration; 232 233 /** 234 * Constructor for MagicNumber Check. 235 * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search. 236 */ 237 public MagicNumberCheck() { 238 Arrays.sort(constantWaiverParentToken); 239 } 240 241 @Override 242 public int[] getDefaultTokens() { 243 return getAcceptableTokens(); 244 } 245 246 @Override 247 public int[] getAcceptableTokens() { 248 return new int[] { 249 TokenTypes.NUM_DOUBLE, 250 TokenTypes.NUM_FLOAT, 251 TokenTypes.NUM_INT, 252 TokenTypes.NUM_LONG, 253 }; 254 } 255 256 @Override 257 public int[] getRequiredTokens() { 258 return CommonUtil.EMPTY_INT_ARRAY; 259 } 260 261 @Override 262 public void visitToken(DetailAST ast) { 263 if ((!ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION)) 264 && !isInIgnoreList(ast) 265 && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) { 266 final DetailAST constantDefAST = findContainingConstantDef(ast); 267 268 if (constantDefAST == null) { 269 if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) { 270 reportMagicNumber(ast); 271 } 272 } 273 else { 274 final boolean found = isMagicNumberExists(ast, constantDefAST); 275 if (found) { 276 reportMagicNumber(ast); 277 } 278 } 279 } 280 } 281 282 /** 283 * Is magic number some where at ast tree. 284 * @param ast ast token 285 * @param constantDefAST constant ast 286 * @return true if magic number is present 287 */ 288 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) { 289 boolean found = false; 290 DetailAST astNode = ast.getParent(); 291 while (astNode != constantDefAST) { 292 final int type = astNode.getType(); 293 if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) { 294 found = true; 295 break; 296 } 297 astNode = astNode.getParent(); 298 } 299 return found; 300 } 301 302 /** 303 * Finds the constant definition that contains aAST. 304 * @param ast the AST 305 * @return the constant def or null if ast is not contained in a constant definition. 306 */ 307 private static DetailAST findContainingConstantDef(DetailAST ast) { 308 DetailAST varDefAST = ast; 309 while (varDefAST != null 310 && varDefAST.getType() != TokenTypes.VARIABLE_DEF 311 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) { 312 varDefAST = varDefAST.getParent(); 313 } 314 DetailAST constantDef = null; 315 316 // no containing variable definition? 317 if (varDefAST != null) { 318 // implicit constant? 319 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST) 320 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 321 constantDef = varDefAST; 322 } 323 else { 324 // explicit constant 325 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS); 326 327 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) { 328 constantDef = varDefAST; 329 } 330 } 331 } 332 return constantDef; 333 } 334 335 /** 336 * Reports aAST as a magic number, includes unary operators as needed. 337 * @param ast the AST node that contains the number to report 338 */ 339 private void reportMagicNumber(DetailAST ast) { 340 String text = ast.getText(); 341 final DetailAST parent = ast.getParent(); 342 DetailAST reportAST = ast; 343 if (parent.getType() == TokenTypes.UNARY_MINUS) { 344 reportAST = parent; 345 text = "-" + text; 346 } 347 else if (parent.getType() == TokenTypes.UNARY_PLUS) { 348 reportAST = parent; 349 text = "+" + text; 350 } 351 log(reportAST, 352 MSG_KEY, 353 text); 354 } 355 356 /** 357 * Determines whether or not the given AST is in a valid hash code method. 358 * A valid hash code method is considered to be a method of the signature 359 * {@code public int hashCode()}. 360 * 361 * @param ast the AST from which to search for an enclosing hash code 362 * method definition 363 * 364 * @return {@code true} if {@code ast} is in the scope of a valid hash code method. 365 */ 366 private static boolean isInHashCodeMethod(DetailAST ast) { 367 boolean inHashCodeMethod = false; 368 369 // if not in a code block, can't be in hashCode() 370 if (ScopeUtil.isInCodeBlock(ast)) { 371 // find the method definition AST 372 DetailAST methodDefAST = ast.getParent(); 373 while (methodDefAST != null 374 && methodDefAST.getType() != TokenTypes.METHOD_DEF) { 375 methodDefAST = methodDefAST.getParent(); 376 } 377 378 if (methodDefAST != null) { 379 // Check for 'hashCode' name. 380 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT); 381 382 if ("hashCode".equals(identAST.getText())) { 383 // Check for no arguments. 384 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS); 385 // we are in a 'public int hashCode()' method! The compiler will ensure 386 // the method returns an 'int' and is public. 387 inHashCodeMethod = paramAST.getChildCount() == 0; 388 } 389 } 390 } 391 return inHashCodeMethod; 392 } 393 394 /** 395 * Decides whether the number of an AST is in the ignore list of this 396 * check. 397 * @param ast the AST to check 398 * @return true if the number of ast is in the ignore list of this check. 399 */ 400 private boolean isInIgnoreList(DetailAST ast) { 401 double value = CheckUtil.parseDouble(ast.getText(), ast.getType()); 402 final DetailAST parent = ast.getParent(); 403 if (parent.getType() == TokenTypes.UNARY_MINUS) { 404 value = -1 * value; 405 } 406 return Arrays.binarySearch(ignoreNumbers, value) >= 0; 407 } 408 409 /** 410 * Determines whether or not the given AST is field declaration. 411 * 412 * @param ast AST from which to search for an enclosing field declaration 413 * 414 * @return {@code true} if {@code ast} is in the scope of field declaration 415 */ 416 private static boolean isFieldDeclaration(DetailAST ast) { 417 DetailAST varDefAST = ast; 418 while (varDefAST != null 419 && varDefAST.getType() != TokenTypes.VARIABLE_DEF) { 420 varDefAST = varDefAST.getParent(); 421 } 422 423 // contains variable declaration 424 // and it is directly inside class declaration 425 return varDefAST != null 426 && varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF; 427 } 428 429 /** 430 * Setter to specify tokens that are allowed in the AST path from the 431 * number literal to the enclosing constant definition. 432 * @param tokens The string representation of the tokens interested in 433 */ 434 public void setConstantWaiverParentToken(String... tokens) { 435 constantWaiverParentToken = new int[tokens.length]; 436 for (int i = 0; i < tokens.length; i++) { 437 constantWaiverParentToken[i] = TokenUtil.getTokenId(tokens[i]); 438 } 439 Arrays.sort(constantWaiverParentToken); 440 } 441 442 /** 443 * Setter to specify non-magic numbers. 444 * @param list list of numbers to ignore. 445 */ 446 public void setIgnoreNumbers(double... list) { 447 if (list.length == 0) { 448 ignoreNumbers = CommonUtil.EMPTY_DOUBLE_ARRAY; 449 } 450 else { 451 ignoreNumbers = new double[list.length]; 452 System.arraycopy(list, 0, ignoreNumbers, 0, list.length); 453 Arrays.sort(ignoreNumbers); 454 } 455 } 456 457 /** 458 * Setter to ignore magic numbers in hashCode methods. 459 * @param ignoreHashCodeMethod decide whether to ignore 460 * hash code methods 461 */ 462 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) { 463 this.ignoreHashCodeMethod = ignoreHashCodeMethod; 464 } 465 466 /** 467 * Setter to ignore magic numbers in annotation declarations. 468 * @param ignoreAnnotation decide whether to ignore annotations 469 */ 470 public void setIgnoreAnnotation(boolean ignoreAnnotation) { 471 this.ignoreAnnotation = ignoreAnnotation; 472 } 473 474 /** 475 * Setter to ignore magic numbers in field declarations. 476 * @param ignoreFieldDeclaration decide whether to ignore magic numbers 477 * in field declaration 478 */ 479 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) { 480 this.ignoreFieldDeclaration = ignoreFieldDeclaration; 481 } 482 483 /** 484 * Determines if the given AST node has a parent node with given token type code. 485 * 486 * @param ast the AST from which to search for annotations 487 * @param type the type code of parent token 488 * 489 * @return {@code true} if the AST node has a parent with given token type. 490 */ 491 private static boolean isChildOf(DetailAST ast, int type) { 492 boolean result = false; 493 DetailAST node = ast; 494 do { 495 if (node.getType() == type) { 496 result = true; 497 break; 498 } 499 node = node.getParent(); 500 } while (node != null); 501 502 return result; 503 } 504 505 }