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