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