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