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#DIV"> 068 * DIV</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELIST"> 070 * ELIST</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 072 * EXPR</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 074 * LITERAL_NEW</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 076 * METHOD_CALL</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS"> 078 * MINUS</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS"> 080 * PLUS</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR"> 082 * STAR</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPECAST"> 084 * TYPECAST</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS"> 086 * UNARY_MINUS</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS"> 088 * UNARY_PLUS</a>. 089 * </li> 090 * <li> 091 * Property {@code ignoreAnnotation} - Ignore magic numbers in annotation declarations. 092 * Type is {@code boolean}. 093 * Default value is {@code false}. 094 * </li> 095 * <li> 096 * Property {@code ignoreAnnotationElementDefaults} - 097 * Ignore magic numbers in annotation elements defaults. 098 * Type is {@code boolean}. 099 * 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 * 132 * <p> 133 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 134 * </p> 135 * 136 * <p> 137 * Violation Message Keys: 138 * </p> 139 * <ul> 140 * <li> 141 * {@code magic.number} 142 * </li> 143 * </ul> 144 * 145 * @since 3.1 146 */ 147@StatelessCheck 148public class MagicNumberCheck extends AbstractCheck { 149 150 /** 151 * A key is pointing to the warning message text in "messages.properties" 152 * file. 153 */ 154 public static final String MSG_KEY = "magic.number"; 155 156 /** 157 * Specify tokens that are allowed in the AST path from the 158 * number literal to the enclosing constant definition. 159 */ 160 @XdocsPropertyType(PropertyType.TOKEN_ARRAY) 161 private BitSet constantWaiverParentToken = TokenUtil.asBitSet( 162 TokenTypes.ASSIGN, 163 TokenTypes.ARRAY_INIT, 164 TokenTypes.EXPR, 165 TokenTypes.UNARY_PLUS, 166 TokenTypes.UNARY_MINUS, 167 TokenTypes.TYPECAST, 168 TokenTypes.ELIST, 169 TokenTypes.LITERAL_NEW, 170 TokenTypes.METHOD_CALL, 171 TokenTypes.STAR, 172 TokenTypes.DIV, 173 TokenTypes.PLUS, 174 TokenTypes.MINUS 175 ); 176 177 /** Specify non-magic numbers. */ 178 private double[] ignoreNumbers = {-1, 0, 1, 2}; 179 180 /** Ignore magic numbers in hashCode methods. */ 181 private boolean ignoreHashCodeMethod; 182 183 /** Ignore magic numbers in annotation declarations. */ 184 private boolean ignoreAnnotation; 185 186 /** Ignore magic numbers in field declarations. */ 187 private boolean ignoreFieldDeclaration; 188 189 /** Ignore magic numbers in annotation elements defaults. */ 190 private boolean ignoreAnnotationElementDefaults = true; 191 192 @Override 193 public int[] getDefaultTokens() { 194 return getAcceptableTokens(); 195 } 196 197 @Override 198 public int[] getAcceptableTokens() { 199 return new int[] { 200 TokenTypes.NUM_DOUBLE, 201 TokenTypes.NUM_FLOAT, 202 TokenTypes.NUM_INT, 203 TokenTypes.NUM_LONG, 204 }; 205 } 206 207 @Override 208 public int[] getRequiredTokens() { 209 return CommonUtil.EMPTY_INT_ARRAY; 210 } 211 212 @Override 213 public void visitToken(DetailAST ast) { 214 if (shouldTestAnnotationArgs(ast) 215 && shouldTestAnnotationDefaults(ast) 216 && !isInIgnoreList(ast) 217 && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) { 218 final DetailAST constantDefAST = findContainingConstantDef(ast); 219 220 if (constantDefAST == null) { 221 if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) { 222 reportMagicNumber(ast); 223 } 224 } 225 else { 226 final boolean found = isMagicNumberExists(ast, constantDefAST); 227 if (found) { 228 reportMagicNumber(ast); 229 } 230 } 231 } 232 } 233 234 /** 235 * Checks if ast is annotation argument and should be checked. 236 * 237 * @param ast token to check 238 * @return true if element is skipped, false otherwise 239 */ 240 private boolean shouldTestAnnotationArgs(DetailAST ast) { 241 return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION); 242 } 243 244 /** 245 * Checks if ast is annotation element default value and should be checked. 246 * 247 * @param ast token to check 248 * @return true if element is skipped, false otherwise 249 */ 250 private boolean shouldTestAnnotationDefaults(DetailAST ast) { 251 return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT); 252 } 253 254 /** 255 * Is magic number somewhere at ast tree. 256 * 257 * @param ast ast token 258 * @param constantDefAST constant ast 259 * @return true if magic number is present 260 */ 261 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) { 262 boolean found = false; 263 DetailAST astNode = ast.getParent(); 264 while (astNode != constantDefAST) { 265 final int type = astNode.getType(); 266 if (!constantWaiverParentToken.get(type)) { 267 found = true; 268 break; 269 } 270 astNode = astNode.getParent(); 271 } 272 return found; 273 } 274 275 /** 276 * Finds the constant definition that contains aAST. 277 * 278 * @param ast the AST 279 * @return the constant def or null if ast is not contained in a constant definition. 280 */ 281 private static DetailAST findContainingConstantDef(DetailAST ast) { 282 DetailAST varDefAST = ast; 283 while (varDefAST != null 284 && varDefAST.getType() != TokenTypes.VARIABLE_DEF 285 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) { 286 varDefAST = varDefAST.getParent(); 287 } 288 DetailAST constantDef = null; 289 290 // no containing variable definition? 291 if (varDefAST != null) { 292 // implicit constant? 293 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST) 294 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 295 constantDef = varDefAST; 296 } 297 else { 298 // explicit constant 299 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS); 300 301 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) { 302 constantDef = varDefAST; 303 } 304 } 305 } 306 return constantDef; 307 } 308 309 /** 310 * Reports aAST as a magic number, includes unary operators as needed. 311 * 312 * @param ast the AST node that contains the number to report 313 */ 314 private void reportMagicNumber(DetailAST ast) { 315 String text = ast.getText(); 316 final DetailAST parent = ast.getParent(); 317 DetailAST reportAST = ast; 318 if (parent.getType() == TokenTypes.UNARY_MINUS) { 319 reportAST = parent; 320 text = "-" + text; 321 } 322 else if (parent.getType() == TokenTypes.UNARY_PLUS) { 323 reportAST = parent; 324 text = "+" + text; 325 } 326 log(reportAST, 327 MSG_KEY, 328 text); 329 } 330 331 /** 332 * Determines whether or not the given AST is in a valid hash code method. 333 * A valid hash code method is considered to be a method of the signature 334 * {@code public int hashCode()}. 335 * 336 * @param ast the AST from which to search for an enclosing hash code 337 * method definition 338 * 339 * @return {@code true} if {@code ast} is in the scope of a valid hash code method. 340 */ 341 private static boolean isInHashCodeMethod(DetailAST ast) { 342 // find the method definition AST 343 DetailAST currentAST = ast; 344 while (currentAST != null 345 && currentAST.getType() != TokenTypes.METHOD_DEF) { 346 currentAST = currentAST.getParent(); 347 } 348 final DetailAST methodDefAST = currentAST; 349 boolean inHashCodeMethod = false; 350 351 if (methodDefAST != null) { 352 // Check for 'hashCode' name. 353 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT); 354 355 if ("hashCode".equals(identAST.getText())) { 356 // Check for no arguments. 357 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS); 358 // we are in a 'public int hashCode()' method! The compiler will ensure 359 // the method returns an 'int' and is public. 360 inHashCodeMethod = !paramAST.hasChildren(); 361 } 362 } 363 return inHashCodeMethod; 364 } 365 366 /** 367 * Decides whether the number of an AST is in the ignore list of this 368 * check. 369 * 370 * @param ast the AST to check 371 * @return true if the number of ast is in the ignore list of this check. 372 */ 373 private boolean isInIgnoreList(DetailAST ast) { 374 double value = CheckUtil.parseDouble(ast.getText(), ast.getType()); 375 final DetailAST parent = ast.getParent(); 376 if (parent.getType() == TokenTypes.UNARY_MINUS) { 377 value = -1 * value; 378 } 379 return Arrays.binarySearch(ignoreNumbers, value) >= 0; 380 } 381 382 /** 383 * Determines whether or not the given AST is field declaration. 384 * 385 * @param ast AST from which to search for an enclosing field declaration 386 * 387 * @return {@code true} if {@code ast} is in the scope of field declaration 388 */ 389 private static boolean isFieldDeclaration(DetailAST ast) { 390 DetailAST varDefAST = null; 391 DetailAST node = ast; 392 while (node != null && node.getType() != TokenTypes.OBJBLOCK) { 393 if (node.getType() == TokenTypes.VARIABLE_DEF) { 394 varDefAST = node; 395 break; 396 } 397 node = node.getParent(); 398 } 399 400 // contains variable declaration 401 // and it is directly inside class or record declaration 402 return varDefAST != null 403 && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF 404 || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF 405 || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW); 406 } 407 408 /** 409 * Setter to specify tokens that are allowed in the AST path from the 410 * number literal to the enclosing constant definition. 411 * 412 * @param tokens The string representation of the tokens interested in 413 * @since 6.11 414 */ 415 public void setConstantWaiverParentToken(String... tokens) { 416 constantWaiverParentToken = TokenUtil.asBitSet(tokens); 417 } 418 419 /** 420 * Setter to specify non-magic numbers. 421 * 422 * @param list numbers to ignore. 423 * @since 3.1 424 */ 425 public void setIgnoreNumbers(double... list) { 426 ignoreNumbers = new double[list.length]; 427 System.arraycopy(list, 0, ignoreNumbers, 0, list.length); 428 Arrays.sort(ignoreNumbers); 429 } 430 431 /** 432 * Setter to ignore magic numbers in hashCode methods. 433 * 434 * @param ignoreHashCodeMethod decide whether to ignore 435 * hash code methods 436 * @since 5.3 437 */ 438 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) { 439 this.ignoreHashCodeMethod = ignoreHashCodeMethod; 440 } 441 442 /** 443 * Setter to ignore magic numbers in annotation declarations. 444 * 445 * @param ignoreAnnotation decide whether to ignore annotations 446 * @since 5.4 447 */ 448 public void setIgnoreAnnotation(boolean ignoreAnnotation) { 449 this.ignoreAnnotation = ignoreAnnotation; 450 } 451 452 /** 453 * Setter to ignore magic numbers in field declarations. 454 * 455 * @param ignoreFieldDeclaration decide whether to ignore magic numbers 456 * in field declaration 457 * @since 6.6 458 */ 459 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) { 460 this.ignoreFieldDeclaration = ignoreFieldDeclaration; 461 } 462 463 /** 464 * Setter to ignore magic numbers in annotation elements defaults. 465 * 466 * @param ignoreAnnotationElementDefaults decide whether to ignore annotation elements defaults 467 * @since 8.23 468 */ 469 public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) { 470 this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults; 471 } 472 473 /** 474 * Determines if the given AST node has a parent node with given token type code. 475 * 476 * @param ast the AST from which to search for annotations 477 * @param type the type code of parent token 478 * 479 * @return {@code true} if the AST node has a parent with given token type. 480 */ 481 private static boolean isChildOf(DetailAST ast, int type) { 482 boolean result = false; 483 DetailAST node = ast; 484 do { 485 if (node.getType() == type) { 486 result = true; 487 break; 488 } 489 node = node.getParent(); 490 } while (node != null); 491 492 return result; 493 } 494 495}