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