001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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; 023import java.util.BitSet; 024 025import com.puppycrawl.tools.checkstyle.PropertyType; 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * <div> 038 * Checks that there are no 039 * <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29"> 040 * "magic numbers"</a> where a magic 041 * number is a numeric literal that is not defined as a constant. 042 * By default, -1, 0, 1, and 2 are not considered to be magic numbers. 043 * </div> 044 * 045 * <p>Constant definition is any variable/field that has 'final' modifier. 046 * It is fine to have one constant defining multiple numeric literals within one expression: 047 * </p> 048 * <pre> 049 * static final int SECONDS_PER_DAY = 24 * 60 * 60; 050 * static final double SPECIAL_RATIO = 4.0 / 3.0; 051 * static final double SPECIAL_SUM = 1 + Math.E; 052 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI; 053 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3); 054 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42); 055 * </pre> 056 * <ul> 057 * <li> 058 * Property {@code constantWaiverParentToken} - Specify tokens that are allowed in the AST path 059 * from the number literal to the enclosing constant definition. 060 * Type is {@code java.lang.String[]}. 061 * Validation type is {@code tokenTypesSet}. 062 * Default value is 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT"> 064 * ARRAY_INIT</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN"> 066 * ASSIGN</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND"> 068 * BAND</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BNOT"> 070 * BNOT</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR"> 072 * BOR</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR"> 074 * BSR</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR"> 076 * BXOR</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON"> 078 * COLON</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV"> 080 * DIV</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST"> 082 * ELIST</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL"> 084 * EQUAL</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 086 * EXPR</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE"> 088 * GE</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT"> 090 * GT</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE"> 092 * LE</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 094 * LITERAL_NEW</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT"> 096 * LT</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 098 * METHOD_CALL</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#MOD"> 102 * MOD</a>, 103 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL"> 104 * NOT_EQUAL</a>, 105 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 106 * PLUS</a>, 107 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 108 * QUESTION</a>, 109 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL"> 110 * SL</a>, 111 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR"> 112 * SR</a>, 113 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 114 * STAR</a>, 115 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST"> 116 * TYPECAST</a>, 117 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS"> 118 * UNARY_MINUS</a>, 119 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS"> 120 * UNARY_PLUS</a>. 121 * </li> 122 * <li> 123 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations. 124 * Type is {@code boolean}. 125 * Default value is {@code false}. 126 * </li> 127 * <li> 128 * Property {@code ignoreAnnotationElementDefaults} - 129 * Ignore magic numbers in annotation elements defaults. 130 * Type is {@code boolean}. 131 * Default value is {@code true}. 132 * </li> 133 * <li> 134 * Property {@code ignoreFieldDeclaration} - Ignore magic numbers in field declarations. 135 * Type is {@code boolean}. 136 * Default value is {@code false}. 137 * </li> 138 * <li> 139 * Property {@code ignoreHashCodeMethod} - Ignore magic numbers in hashCode methods. 140 * Type is {@code boolean}. 141 * Default value is {@code false}. 142 * </li> 143 * <li> 144 * Property {@code ignoreNumbers} - Specify non-magic numbers. 145 * Type is {@code double[]}. 146 * Default value is {@code -1, 0, 1, 2}. 147 * </li> 148 * <li> 149 * Property {@code tokens} - tokens to check 150 * Type is {@code java.lang.String[]}. 151 * Validation type is {@code tokenSet}. 152 * Default value is: 153 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE"> 154 * NUM_DOUBLE</a>, 155 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT"> 156 * NUM_FLOAT</a>, 157 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT"> 158 * NUM_INT</a>, 159 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG"> 160 * NUM_LONG</a>. 161 * </li> 162 * </ul> 163 * 164 * <p> 165 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 166 * </p> 167 * 168 * <p> 169 * Violation Message Keys: 170 * </p> 171 * <ul> 172 * <li> 173 * {@code magic.number} 174 * </li> 175 * </ul> 176 * 177 * @since 3.1 178 */ 179@StatelessCheck 180public class MagicNumberCheck extends AbstractCheck { 181 182 /** 183 * A key is pointing to the warning message text in "messages.properties" 184 * file. 185 */ 186 public static final String MSG_KEY = "magic.number"; 187 188 /** 189 * Specify tokens that are allowed in the AST path from the 190 * number literal to the enclosing constant definition. 191 */ 192 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 193 private BitSet constantWaiverParentToken = TokenUtil.asBitSet( 194 TokenTypes.ASSIGN, 195 TokenTypes.ARRAY_INIT, 196 TokenTypes.EXPR, 197 TokenTypes.UNARY_PLUS, 198 TokenTypes.UNARY_MINUS, 199 TokenTypes.TYPECAST, 200 TokenTypes.ELIST, 201 TokenTypes.LITERAL_NEW, 202 TokenTypes.METHOD_CALL, 203 TokenTypes.STAR, 204 TokenTypes.DIV, 205 TokenTypes.PLUS, 206 TokenTypes.MINUS, 207 TokenTypes.QUESTION, 208 TokenTypes.COLON, 209 TokenTypes.EQUAL, 210 TokenTypes.NOT_EQUAL, 211 TokenTypes.MOD, 212 TokenTypes.SR, 213 TokenTypes.BSR, 214 TokenTypes.GE, 215 TokenTypes.GT, 216 TokenTypes.SL, 217 TokenTypes.LE, 218 TokenTypes.LT, 219 TokenTypes.BXOR, 220 TokenTypes.BOR, 221 TokenTypes.BNOT, 222 TokenTypes.BAND 223 ); 224 225 /** Specify non-magic numbers. */ 226 private double[] ignoreNumbers = {-1, 0, 1, 2}; 227 228 /** Ignore magic numbers in hashCode methods. */ 229 private boolean ignoreHashCodeMethod; 230 231 /** Ignore magic numbers in annotation declarations. */ 232 private boolean ignoreAnnotation; 233 234 /** Ignore magic numbers in field declarations. */ 235 private boolean ignoreFieldDeclaration; 236 237 /** Ignore magic numbers in annotation elements defaults. */ 238 private boolean ignoreAnnotationElementDefaults = true; 239 240 @Override 241 public int[] getDefaultTokens() { 242 return getAcceptableTokens(); 243 } 244 245 @Override 246 public int[] getAcceptableTokens() { 247 return new int[] { 248 TokenTypes.NUM_DOUBLE, 249 TokenTypes.NUM_FLOAT, 250 TokenTypes.NUM_INT, 251 TokenTypes.NUM_LONG, 252 }; 253 } 254 255 @Override 256 public int[] getRequiredTokens() { 257 return CommonUtil.EMPTY_INT_ARRAY; 258 } 259 260 @Override 261 public void visitToken(DetailAST ast) { 262 if (shouldTestAnnotationArgs(ast) 263 && shouldTestAnnotationDefaults(ast) 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 * Checks if ast is annotation argument and should be checked. 284 * 285 * @param ast token to check 286 * @return true if element is skipped, false otherwise 287 */ 288 private boolean shouldTestAnnotationArgs(DetailAST ast) { 289 return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION); 290 } 291 292 /** 293 * Checks if ast is annotation element default value and should be checked. 294 * 295 * @param ast token to check 296 * @return true if element is skipped, false otherwise 297 */ 298 private boolean shouldTestAnnotationDefaults(DetailAST ast) { 299 return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT); 300 } 301 302 /** 303 * Is magic number somewhere at ast tree. 304 * 305 * @param ast ast token 306 * @param constantDefAST constant ast 307 * @return true if magic number is present 308 */ 309 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) { 310 boolean found = false; 311 DetailAST astNode = ast.getParent(); 312 while (astNode != constantDefAST) { 313 final int type = astNode.getType(); 314 if (!constantWaiverParentToken.get(type)) { 315 found = true; 316 break; 317 } 318 astNode = astNode.getParent(); 319 } 320 return found; 321 } 322 323 /** 324 * Finds the constant definition that contains aAST. 325 * 326 * @param ast the AST 327 * @return the constant def or null if ast is not contained in a constant definition. 328 */ 329 private static DetailAST findContainingConstantDef(DetailAST ast) { 330 DetailAST varDefAST = ast; 331 while (varDefAST != null 332 && varDefAST.getType() != TokenTypes.VARIABLE_DEF 333 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) { 334 varDefAST = varDefAST.getParent(); 335 } 336 DetailAST constantDef = null; 337 338 // no containing variable definition? 339 if (varDefAST != null) { 340 // implicit constant? 341 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST) 342 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 343 constantDef = varDefAST; 344 } 345 else { 346 // explicit constant 347 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS); 348 349 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) { 350 constantDef = varDefAST; 351 } 352 } 353 } 354 return constantDef; 355 } 356 357 /** 358 * Reports aAST as a magic number, includes unary operators as needed. 359 * 360 * @param ast the AST node that contains the number to report 361 */ 362 private void reportMagicNumber(DetailAST ast) { 363 String text = ast.getText(); 364 final DetailAST parent = ast.getParent(); 365 DetailAST reportAST = ast; 366 if (parent.getType() == TokenTypes.UNARY_MINUS) { 367 reportAST = parent; 368 text = "-" + text; 369 } 370 else if (parent.getType() == TokenTypes.UNARY_PLUS) { 371 reportAST = parent; 372 text = "+" + text; 373 } 374 log(reportAST, 375 MSG_KEY, 376 text); 377 } 378 379 /** 380 * Determines whether or not the given AST is in a valid hash code method. 381 * A valid hash code method is considered to be a method of the signature 382 * {@code public int hashCode()}. 383 * 384 * @param ast the AST from which to search for an enclosing hash code 385 * method definition 386 * 387 * @return {@code true} if {@code ast} is in the scope of a valid hash code method. 388 */ 389 private static boolean isInHashCodeMethod(DetailAST ast) { 390 // find the method definition AST 391 DetailAST currentAST = ast; 392 while (currentAST != null 393 && currentAST.getType() != TokenTypes.METHOD_DEF) { 394 currentAST = currentAST.getParent(); 395 } 396 final DetailAST methodDefAST = currentAST; 397 boolean inHashCodeMethod = false; 398 399 if (methodDefAST != null) { 400 // Check for 'hashCode' name. 401 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT); 402 403 if ("hashCode".equals(identAST.getText())) { 404 // Check for no arguments. 405 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS); 406 // we are in a 'public int hashCode()' method! The compiler will ensure 407 // the method returns an 'int' and is public. 408 inHashCodeMethod = !paramAST.hasChildren(); 409 } 410 } 411 return inHashCodeMethod; 412 } 413 414 /** 415 * Decides whether the number of an AST is in the ignore list of this 416 * check. 417 * 418 * @param ast the AST to check 419 * @return true if the number of ast is in the ignore list of this check. 420 */ 421 private boolean isInIgnoreList(DetailAST ast) { 422 double value = CheckUtil.parseDouble(ast.getText(), ast.getType()); 423 final DetailAST parent = ast.getParent(); 424 if (parent.getType() == TokenTypes.UNARY_MINUS) { 425 value = -1 * value; 426 } 427 return Arrays.binarySearch(ignoreNumbers, value) >= 0; 428 } 429 430 /** 431 * Determines whether or not the given AST is field declaration. 432 * 433 * @param ast AST from which to search for an enclosing field declaration 434 * 435 * @return {@code true} if {@code ast} is in the scope of field declaration 436 */ 437 private static boolean isFieldDeclaration(DetailAST ast) { 438 DetailAST varDefAST = null; 439 DetailAST node = ast; 440 while (node != null && node.getType() != TokenTypes.OBJBLOCK) { 441 if (node.getType() == TokenTypes.VARIABLE_DEF) { 442 varDefAST = node; 443 break; 444 } 445 node = node.getParent(); 446 } 447 448 // contains variable declaration 449 // and it is directly inside class or record declaration 450 return varDefAST != null 451 && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF 452 || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF 453 || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW); 454 } 455 456 /** 457 * Setter to specify tokens that are allowed in the AST path from the 458 * number literal to the enclosing constant definition. 459 * 460 * @param tokens The string representation of the tokens interested in 461 * @since 6.11 462 */ 463 public void setConstantWaiverParentToken(String... tokens) { 464 constantWaiverParentToken = TokenUtil.asBitSet(tokens); 465 } 466 467 /** 468 * Setter to specify non-magic numbers. 469 * 470 * @param list numbers to ignore. 471 * @since 3.1 472 */ 473 public void setIgnoreNumbers(double... list) { 474 ignoreNumbers = new double[list.length]; 475 System.arraycopy(list, 0, ignoreNumbers, 0, list.length); 476 Arrays.sort(ignoreNumbers); 477 } 478 479 /** 480 * Setter to ignore magic numbers in hashCode methods. 481 * 482 * @param ignoreHashCodeMethod decide whether to ignore 483 * hash code methods 484 * @since 5.3 485 */ 486 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) { 487 this.ignoreHashCodeMethod = ignoreHashCodeMethod; 488 } 489 490 /** 491 * Setter to ignore magic numbers in annotation declarations. 492 * 493 * @param ignoreAnnotation decide whether to ignore annotations 494 * @since 5.4 495 */ 496 public void setIgnoreAnnotation(boolean ignoreAnnotation) { 497 this.ignoreAnnotation = ignoreAnnotation; 498 } 499 500 /** 501 * Setter to ignore magic numbers in field declarations. 502 * 503 * @param ignoreFieldDeclaration decide whether to ignore magic numbers 504 * in field declaration 505 * @since 6.6 506 */ 507 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) { 508 this.ignoreFieldDeclaration = ignoreFieldDeclaration; 509 } 510 511 /** 512 * Setter to ignore magic numbers in annotation elements defaults. 513 * 514 * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults 515 * @since 8.23 516 */ 517 public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) { 518 this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults; 519 } 520 521 /** 522 * Determines if the given AST node has a parent node with given token type code. 523 * 524 * @param ast the AST from which to search for annotations 525 * @param type the type code of parent token 526 * 527 * @return {@code true} if the AST node has a parent with given token type. 528 */ 529 private static boolean isChildOf(DetailAST ast, int type) { 530 boolean result = false; 531 DetailAST node = ast; 532 do { 533 if (node.getType() == type) { 534 result = true; 535 break; 536 } 537 node = node.getParent(); 538 } while (node != null); 539 540 return result; 541 } 542 543}