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