001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 031import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 032 033/** 034 * <p> 035 * Checks that there are no <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29"> 036 * "magic numbers"</a> where a magic 037 * number is a numeric literal that is not defined as a constant. 038 * By default, -1, 0, 1, and 2 are not considered to be magic numbers. 039 * </p> 040 * 041 * <p>Constant definition is any variable/field that has 'final' modifier. 042 * It is fine to have one constant defining multiple numeric literals within one expression: 043 * <pre> 044 * {@code static final int SECONDS_PER_DAY = 24 * 60 * 60; 045 * static final double SPECIAL_RATIO = 4.0 / 3.0; 046 * static final double SPECIAL_SUM = 1 + Math.E; 047 * static final double SPECIAL_DIFFERENCE = 4 - Math.PI; 048 * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3); 049 * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);} 050 * </pre> 051 * 052 * <p>Check have following options: 053 * ignoreHashCodeMethod - ignore magic numbers in hashCode methods; 054 * ignoreAnnotation - ignore magic numbers in annotation declarations; 055 * ignoreFieldDeclaration - ignore magic numbers in field declarations. 056 * <p> 057 * To configure the check with default configuration: 058 * </p> 059 * <pre> 060 * <module name="MagicNumber"/> 061 * </pre> 062 * <p> 063 * results is following violations: 064 * </p> 065 * <pre> 066 * {@code 067 * {@literal @}MyAnnotation(6) // violation 068 * class MyClass { 069 * private field = 7; // violation 070 * 071 * void foo() { 072 * int i = i + 1; // no violation 073 * int j = j + 8; // violation 074 * } 075 * } 076 * } 077 * </pre> 078 * <p> 079 * To configure the check so that it checks floating-point numbers 080 * that are not 0, 0.5, or 1: 081 * </p> 082 * <pre> 083 * <module name="MagicNumber"> 084 * <property name="tokens" value="NUM_DOUBLE, NUM_FLOAT"/> 085 * <property name="ignoreNumbers" value="0, 0.5, 1"/> 086 * <property name="ignoreFieldDeclaration" value="true"/> 087 * <property name="ignoreAnnotation" value="true"/> 088 * </module> 089 * </pre> 090 * <p> 091 * results is following violations: 092 * </p> 093 * <pre> 094 * {@code 095 * {@literal @}MyAnnotation(6) // no violation 096 * class MyClass { 097 * private field = 7; // no violation 098 * 099 * void foo() { 100 * int i = i + 1; // no violation 101 * int j = j + (int)0.5; // no violation 102 * } 103 * } 104 * } 105 * </pre> 106 * <p> 107 * Config example of constantWaiverParentToken option: 108 * </p> 109 * <pre> 110 * <module name="MagicNumber"> 111 * <property name="constantWaiverParentToken" value="ASSIGN,ARRAY_INIT,EXPR, 112 * UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS "/> 113 * </module> 114 * </pre> 115 * <p> 116 * result is following violation: 117 * </p> 118 * <pre> 119 * {@code 120 * class TestMethodCall { 121 * public void method2() { 122 * final TestMethodCall dummyObject = new TestMethodCall(62); //violation 123 * final int a = 3; // ok as waiver is ASSIGN 124 * final int [] b = {4, 5} // ok as waiver is ARRAY_INIT 125 * final int c = -3; // ok as waiver is UNARY_MINUS 126 * final int d = +4; // ok as waiver is UNARY_PLUS 127 * final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL 128 * final int x = 3 * 4; // violation 129 * final int y = 3 / 4; // ok as waiver is DIV 130 * final int z = 3 + 4; // ok as waiver is PLUS 131 * final int w = 3 - 4; // violation 132 * final int x = (int)(3.4); //ok as waiver is TYPECAST 133 * } 134 * } 135 * } 136 * </pre> 137 * @author Rick Giles 138 * @author Lars Kühne 139 * @author Daniel Solano Gómez 140 */ 141@StatelessCheck 142public class MagicNumberCheck extends AbstractCheck { 143 144 /** 145 * A key is pointing to the warning message text in "messages.properties" 146 * file. 147 */ 148 public static final String MSG_KEY = "magic.number"; 149 150 /** 151 * The token types that are allowed in the AST path from the 152 * number literal to the enclosing constant definition. 153 */ 154 private int[] constantWaiverParentToken = { 155 TokenTypes.ASSIGN, 156 TokenTypes.ARRAY_INIT, 157 TokenTypes.EXPR, 158 TokenTypes.UNARY_PLUS, 159 TokenTypes.UNARY_MINUS, 160 TokenTypes.TYPECAST, 161 TokenTypes.ELIST, 162 TokenTypes.LITERAL_NEW, 163 TokenTypes.METHOD_CALL, 164 TokenTypes.STAR, 165 TokenTypes.DIV, 166 TokenTypes.PLUS, 167 TokenTypes.MINUS, 168 }; 169 170 /** The numbers to ignore in the check, sorted. */ 171 private double[] ignoreNumbers = {-1, 0, 1, 2}; 172 173 /** Whether to ignore magic numbers in a hash code method. */ 174 private boolean ignoreHashCodeMethod; 175 176 /** Whether to ignore magic numbers in annotation. */ 177 private boolean ignoreAnnotation; 178 179 /** Whether to ignore magic numbers in field declaration. */ 180 private boolean ignoreFieldDeclaration; 181 182 /** 183 * Constructor for MagicNumber Check. 184 * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search. 185 */ 186 public MagicNumberCheck() { 187 Arrays.sort(constantWaiverParentToken); 188 } 189 190 @Override 191 public int[] getDefaultTokens() { 192 return getAcceptableTokens(); 193 } 194 195 @Override 196 public int[] getAcceptableTokens() { 197 return new int[] { 198 TokenTypes.NUM_DOUBLE, 199 TokenTypes.NUM_FLOAT, 200 TokenTypes.NUM_INT, 201 TokenTypes.NUM_LONG, 202 }; 203 } 204 205 @Override 206 public int[] getRequiredTokens() { 207 return CommonUtils.EMPTY_INT_ARRAY; 208 } 209 210 @Override 211 public void visitToken(DetailAST ast) { 212 if ((!ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION)) 213 && !isInIgnoreList(ast) 214 && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) { 215 final DetailAST constantDefAST = findContainingConstantDef(ast); 216 217 if (constantDefAST == null) { 218 if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) { 219 reportMagicNumber(ast); 220 } 221 } 222 else { 223 final boolean found = isMagicNumberExists(ast, constantDefAST); 224 if (found) { 225 reportMagicNumber(ast); 226 } 227 } 228 } 229 } 230 231 /** 232 * Is magic number some where at ast tree. 233 * @param ast ast token 234 * @param constantDefAST constant ast 235 * @return true if magic number is present 236 */ 237 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) { 238 boolean found = false; 239 DetailAST astNode = ast.getParent(); 240 while (astNode != constantDefAST) { 241 final int type = astNode.getType(); 242 if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) { 243 found = true; 244 break; 245 } 246 astNode = astNode.getParent(); 247 } 248 return found; 249 } 250 251 /** 252 * Finds the constant definition that contains aAST. 253 * @param ast the AST 254 * @return the constant def or null if ast is not contained in a constant definition. 255 */ 256 private static DetailAST findContainingConstantDef(DetailAST ast) { 257 DetailAST varDefAST = ast; 258 while (varDefAST != null 259 && varDefAST.getType() != TokenTypes.VARIABLE_DEF 260 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) { 261 varDefAST = varDefAST.getParent(); 262 } 263 DetailAST constantDef = null; 264 265 // no containing variable definition? 266 if (varDefAST != null) { 267 // implicit constant? 268 if (ScopeUtils.isInInterfaceOrAnnotationBlock(varDefAST) 269 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 270 constantDef = varDefAST; 271 } 272 else { 273 // explicit constant 274 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS); 275 276 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) { 277 constantDef = varDefAST; 278 } 279 } 280 } 281 return constantDef; 282 } 283 284 /** 285 * Reports aAST as a magic number, includes unary operators as needed. 286 * @param ast the AST node that contains the number to report 287 */ 288 private void reportMagicNumber(DetailAST ast) { 289 String text = ast.getText(); 290 final DetailAST parent = ast.getParent(); 291 DetailAST reportAST = ast; 292 if (parent.getType() == TokenTypes.UNARY_MINUS) { 293 reportAST = parent; 294 text = "-" + text; 295 } 296 else if (parent.getType() == TokenTypes.UNARY_PLUS) { 297 reportAST = parent; 298 text = "+" + text; 299 } 300 log(reportAST.getLineNo(), 301 reportAST.getColumnNo(), 302 MSG_KEY, 303 text); 304 } 305 306 /** 307 * Determines whether or not the given AST is in a valid hash code method. 308 * A valid hash code method is considered to be a method of the signature 309 * {@code public int hashCode()}. 310 * 311 * @param ast the AST from which to search for an enclosing hash code 312 * method definition 313 * 314 * @return {@code true} if {@code ast} is in the scope of a valid hash code method. 315 */ 316 private static boolean isInHashCodeMethod(DetailAST ast) { 317 boolean inHashCodeMethod = false; 318 319 // if not in a code block, can't be in hashCode() 320 if (ScopeUtils.isInCodeBlock(ast)) { 321 // find the method definition AST 322 DetailAST methodDefAST = ast.getParent(); 323 while (methodDefAST != null 324 && methodDefAST.getType() != TokenTypes.METHOD_DEF) { 325 methodDefAST = methodDefAST.getParent(); 326 } 327 328 if (methodDefAST != null) { 329 // Check for 'hashCode' name. 330 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT); 331 332 if ("hashCode".equals(identAST.getText())) { 333 // Check for no arguments. 334 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS); 335 // we are in a 'public int hashCode()' method! The compiler will ensure 336 // the method returns an 'int' and is public. 337 inHashCodeMethod = paramAST.getChildCount() == 0; 338 } 339 } 340 } 341 return inHashCodeMethod; 342 } 343 344 /** 345 * Decides whether the number of an AST is in the ignore list of this 346 * check. 347 * @param ast the AST to check 348 * @return true if the number of ast is in the ignore list of this check. 349 */ 350 private boolean isInIgnoreList(DetailAST ast) { 351 double value = CheckUtils.parseDouble(ast.getText(), ast.getType()); 352 final DetailAST parent = ast.getParent(); 353 if (parent.getType() == TokenTypes.UNARY_MINUS) { 354 value = -1 * value; 355 } 356 return Arrays.binarySearch(ignoreNumbers, value) >= 0; 357 } 358 359 /** 360 * Determines whether or not the given AST is field declaration. 361 * 362 * @param ast AST from which to search for an enclosing field declaration 363 * 364 * @return {@code true} if {@code ast} is in the scope of field declaration 365 */ 366 private static boolean isFieldDeclaration(DetailAST ast) { 367 DetailAST varDefAST = ast; 368 while (varDefAST != null 369 && varDefAST.getType() != TokenTypes.VARIABLE_DEF) { 370 varDefAST = varDefAST.getParent(); 371 } 372 373 // contains variable declaration 374 // and it is directly inside class declaration 375 return varDefAST != null 376 && varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF; 377 } 378 379 /** 380 * Sets the tokens which are allowed between Magic Number and defined Object. 381 * @param tokens The string representation of the tokens interested in 382 */ 383 public void setConstantWaiverParentToken(String... tokens) { 384 constantWaiverParentToken = new int[tokens.length]; 385 for (int i = 0; i < tokens.length; i++) { 386 constantWaiverParentToken[i] = TokenUtils.getTokenId(tokens[i]); 387 } 388 Arrays.sort(constantWaiverParentToken); 389 } 390 391 /** 392 * Sets the numbers to ignore in the check. 393 * BeanUtils converts numeric token list to double array automatically. 394 * @param list list of numbers to ignore. 395 */ 396 public void setIgnoreNumbers(double... list) { 397 if (list.length == 0) { 398 ignoreNumbers = CommonUtils.EMPTY_DOUBLE_ARRAY; 399 } 400 else { 401 ignoreNumbers = new double[list.length]; 402 System.arraycopy(list, 0, ignoreNumbers, 0, list.length); 403 Arrays.sort(ignoreNumbers); 404 } 405 } 406 407 /** 408 * Set whether to ignore hashCode methods. 409 * @param ignoreHashCodeMethod decide whether to ignore 410 * hash code methods 411 */ 412 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) { 413 this.ignoreHashCodeMethod = ignoreHashCodeMethod; 414 } 415 416 /** 417 * Set whether to ignore Annotations. 418 * @param ignoreAnnotation decide whether to ignore annotations 419 */ 420 public void setIgnoreAnnotation(boolean ignoreAnnotation) { 421 this.ignoreAnnotation = ignoreAnnotation; 422 } 423 424 /** 425 * Set whether to ignore magic numbers in field declaration. 426 * @param ignoreFieldDeclaration decide whether to ignore magic numbers 427 * in field declaration 428 */ 429 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) { 430 this.ignoreFieldDeclaration = ignoreFieldDeclaration; 431 } 432 433 /** 434 * Determines if the given AST node has a parent node with given token type code. 435 * 436 * @param ast the AST from which to search for annotations 437 * @param type the type code of parent token 438 * 439 * @return {@code true} if the AST node has a parent with given token type. 440 */ 441 private static boolean isChildOf(DetailAST ast, int type) { 442 boolean result = false; 443 DetailAST node = ast; 444 do { 445 if (node.getType() == type) { 446 result = true; 447 break; 448 } 449 node = node.getParent(); 450 } while (node != null); 451 452 return result; 453 } 454 455}