001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.Locale; 023import java.util.Objects; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import com.google.common.collect.Sets; 028import com.puppycrawl.tools.checkstyle.api.Check; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.Scope; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 033import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 034 035/** 036 * Checks that a local variable or a parameter does not shadow 037 * a field that is defined in the same class. 038 * 039 * <p>An example of how to configure the check is: 040 * <pre> 041 * <module name="HiddenField"/> 042 * </pre> 043 * 044 * <p>An example of how to configure the check so that it checks variables but not 045 * parameters is: 046 * <pre> 047 * <module name="HiddenField"> 048 * <property name="tokens" value="VARIABLE_DEF"/> 049 * </module> 050 * </pre> 051 * 052 * <p>An example of how to configure the check so that it ignores the parameter of 053 * a setter method is: 054 * <pre> 055 * <module name="HiddenField"> 056 * <property name="ignoreSetter" value="true"/> 057 * </module> 058 * </pre> 059 * 060 * <p>A method is recognized as a setter if it is in the following form 061 * <pre> 062 * ${returnType} set${Name}(${anyType} ${name}) { ... } 063 * </pre> 064 * where ${anyType} is any primitive type, class or interface name; 065 * ${name} is name of the variable that is being set and ${Name} its 066 * capitalized form that appears in the method name. By default it is expected 067 * that setter returns void, i.e. ${returnType} is 'void'. For example 068 * <pre> 069 * void setTime(long time) { ... } 070 * </pre> 071 * Any other return types will not let method match a setter pattern. However, 072 * by setting <em>setterCanReturnItsClass</em> property to <em>true</em> 073 * definition of a setter is expanded, so that setter return type can also be 074 * a class in which setter is declared. For example 075 * <pre> 076 * class PageBuilder { 077 * PageBuilder setName(String name) { ... } 078 * } 079 * </pre> 080 * Such methods are known as chain-setters and a common when Builder-pattern 081 * is used. Property <em>setterCanReturnItsClass</em> has effect only if 082 * <em>ignoreSetter</em> is set to true. 083 * 084 * <p>An example of how to configure the check so that it ignores the parameter 085 * of either a setter that returns void or a chain-setter. 086 * <pre> 087 * <module name="HiddenField"> 088 * <property name="ignoreSetter" value="true"/> 089 * <property name="setterCanReturnItsClass" value="true"/> 090 * </module> 091 * </pre> 092 * 093 * <p>An example of how to configure the check so that it ignores constructor 094 * parameters is: 095 * <pre> 096 * <module name="HiddenField"> 097 * <property name="ignoreConstructorParameter" value="true"/> 098 * </module> 099 * </pre> 100 * 101 * <p>An example of how to configure the check so that it ignores variables and parameters 102 * named 'test': 103 * <pre> 104 * <module name="HiddenField"> 105 * <property name="ignoreFormat" value="^test$"/> 106 * </module> 107 * </pre> 108 * 109 * <pre> 110 * {@code 111 * class SomeClass 112 * { 113 * private List<String> test; 114 * 115 * private void addTest(List<String> test) // no violation 116 * { 117 * this.test.addAll(test); 118 * } 119 * 120 * private void foo() 121 * { 122 * final List<String> test = new ArrayList<>(); // no violation 123 * ... 124 * } 125 * } 126 * } 127 * </pre> 128 * 129 * @author Dmitri Priimak 130 */ 131public class HiddenFieldCheck 132 extends Check { 133 /** 134 * A key is pointing to the warning message text in "messages.properties" 135 * file. 136 */ 137 public static final String MSG_KEY = "hidden.field"; 138 139 /** Stack of sets of field names, 140 * one for each class of a set of nested classes. 141 */ 142 private FieldFrame frame; 143 144 /** Pattern for names of variables and parameters to ignore. */ 145 private Pattern regexp; 146 147 /** Controls whether to check the parameter of a property setter method. */ 148 private boolean ignoreSetter; 149 150 /** 151 * If ignoreSetter is set to true then this variable controls what 152 * the setter method can return By default setter must return void. 153 * However, is this variable is set to true then setter can also 154 * return class in which is declared. 155 */ 156 private boolean setterCanReturnItsClass; 157 158 /** Controls whether to check the parameter of a constructor. */ 159 private boolean ignoreConstructorParameter; 160 161 /** Controls whether to check the parameter of abstract methods. */ 162 private boolean ignoreAbstractMethods; 163 164 @Override 165 public int[] getDefaultTokens() { 166 return getAcceptableTokens(); 167 } 168 169 @Override 170 public int[] getAcceptableTokens() { 171 return new int[] { 172 TokenTypes.VARIABLE_DEF, 173 TokenTypes.PARAMETER_DEF, 174 TokenTypes.CLASS_DEF, 175 TokenTypes.ENUM_DEF, 176 TokenTypes.ENUM_CONSTANT_DEF, 177 TokenTypes.LAMBDA, 178 }; 179 } 180 181 @Override 182 public int[] getRequiredTokens() { 183 return new int[] { 184 TokenTypes.CLASS_DEF, 185 TokenTypes.ENUM_DEF, 186 TokenTypes.ENUM_CONSTANT_DEF, 187 }; 188 } 189 190 @Override 191 public void beginTree(DetailAST rootAST) { 192 frame = new FieldFrame(null, true, null); 193 } 194 195 @Override 196 public void visitToken(DetailAST ast) { 197 final int type = ast.getType(); 198 switch (type) { 199 case TokenTypes.VARIABLE_DEF: 200 case TokenTypes.PARAMETER_DEF: 201 processVariable(ast); 202 break; 203 case TokenTypes.LAMBDA: 204 processLambda(ast); 205 break; 206 default: 207 visitOtherTokens(ast, type); 208 } 209 } 210 211 /** 212 * Process a lambda token. 213 * Checks whether a lambda parameter shadows a field. 214 * Note, that when parameter of lambda expression is untyped, 215 * ANTLR parses the parameter as an identifier. 216 * @param ast the lambda token. 217 */ 218 private void processLambda(DetailAST ast) { 219 final DetailAST firstChild = ast.getFirstChild(); 220 if (firstChild.getType() == TokenTypes.IDENT) { 221 final String untypedLambdaParameterName = firstChild.getText(); 222 if (isStaticOrInstanceField(firstChild, untypedLambdaParameterName)) { 223 log(firstChild, MSG_KEY, untypedLambdaParameterName); 224 } 225 } 226 else { 227 // Type of lambda parameter is not omitted. 228 processVariable(ast); 229 } 230 } 231 232 /** 233 * Called to process tokens other than {@link TokenTypes#VARIABLE_DEF} 234 * and {@link TokenTypes#PARAMETER_DEF}. 235 * 236 * @param ast token to process 237 * @param type type of the token 238 */ 239 private void visitOtherTokens(DetailAST ast, int type) { 240 //A more thorough check of enum constant class bodies is 241 //possible (checking for hidden fields against the enum 242 //class body in addition to enum constant class bodies) 243 //but not attempted as it seems out of the scope of this 244 //check. 245 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS); 246 final boolean isStaticInnerType = 247 typeMods != null 248 && typeMods.branchContains(TokenTypes.LITERAL_STATIC); 249 final String frameName; 250 251 if (type == TokenTypes.CLASS_DEF || type == TokenTypes.ENUM_DEF) { 252 frameName = ast.findFirstToken(TokenTypes.IDENT).getText(); 253 } 254 else { 255 frameName = null; 256 } 257 final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName); 258 259 //add fields to container 260 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 261 // enum constants may not have bodies 262 if (objBlock != null) { 263 DetailAST child = objBlock.getFirstChild(); 264 while (child != null) { 265 if (child.getType() == TokenTypes.VARIABLE_DEF) { 266 final String name = 267 child.findFirstToken(TokenTypes.IDENT).getText(); 268 final DetailAST mods = 269 child.findFirstToken(TokenTypes.MODIFIERS); 270 if (mods.branchContains(TokenTypes.LITERAL_STATIC)) { 271 newFrame.addStaticField(name); 272 } 273 else { 274 newFrame.addInstanceField(name); 275 } 276 } 277 child = child.getNextSibling(); 278 } 279 } 280 // push container 281 frame = newFrame; 282 } 283 284 @Override 285 public void leaveToken(DetailAST ast) { 286 if (ast.getType() == TokenTypes.CLASS_DEF 287 || ast.getType() == TokenTypes.ENUM_DEF 288 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 289 //pop 290 frame = frame.getParent(); 291 } 292 } 293 294 /** 295 * Process a variable token. 296 * Check whether a local variable or parameter shadows a field. 297 * Store a field for later comparison with local variables and parameters. 298 * @param ast the variable token. 299 */ 300 private void processVariable(DetailAST ast) { 301 if (!ScopeUtils.isInInterfaceOrAnnotationBlock(ast) 302 && (ScopeUtils.isLocalVariableDef(ast) 303 || ast.getType() == TokenTypes.PARAMETER_DEF)) { 304 // local variable or parameter. Does it shadow a field? 305 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 306 final String name = nameAST.getText(); 307 308 if ((isStaticFieldHiddenFromAnonymousClass(ast, name) 309 || isStaticOrInstanceField(ast, name)) 310 && !isMatchingRegexp(name) 311 && !isIgnoredParam(ast, name)) { 312 log(nameAST, MSG_KEY, name); 313 } 314 } 315 } 316 317 /** 318 * Checks whether a static field is hidden from closure. 319 * @param nameAST local variable or parameter. 320 * @param name field name. 321 * @return true if static field is hidden from closure. 322 */ 323 private boolean isStaticFieldHiddenFromAnonymousClass(DetailAST nameAST, String name) { 324 return isInStatic(nameAST) 325 && frame.containsStaticField(name); 326 } 327 328 /** 329 * Checks whether method or constructor parameter is ignored. 330 * @param ast the parameter token. 331 * @param name the parameter name. 332 * @return true if parameter is ignored. 333 */ 334 private boolean isIgnoredParam(DetailAST ast, String name) { 335 return isIgnoredSetterParam(ast, name) 336 || isIgnoredConstructorParam(ast) 337 || isIgnoredParamOfAbstractMethod(ast); 338 } 339 340 /** 341 * Check for static or instance field. 342 * @param ast token 343 * @param name identifier of token 344 * @return true if static or instance field 345 */ 346 private boolean isStaticOrInstanceField(DetailAST ast, String name) { 347 return frame.containsStaticField(name) 348 || !isInStatic(ast) && frame.containsInstanceField(name); 349 } 350 351 /** 352 * Check name by regExp. 353 * @param name string value to check 354 * @return true is regexp is matching 355 */ 356 private boolean isMatchingRegexp(String name) { 357 return regexp != null && regexp.matcher(name).find(); 358 } 359 360 /** 361 * Determines whether an AST node is in a static method or static 362 * initializer. 363 * @param ast the node to check. 364 * @return true if ast is in a static method or a static block; 365 */ 366 private static boolean isInStatic(DetailAST ast) { 367 DetailAST parent = ast.getParent(); 368 boolean inStatic = false; 369 370 while (parent != null && !inStatic) { 371 if (parent.getType() == TokenTypes.STATIC_INIT) { 372 inStatic = true; 373 } 374 else if (parent.getType() == TokenTypes.METHOD_DEF 375 && !ScopeUtils.isInScope(parent, Scope.ANONINNER) 376 || parent.getType() == TokenTypes.VARIABLE_DEF) { 377 final DetailAST mods = 378 parent.findFirstToken(TokenTypes.MODIFIERS); 379 inStatic = mods.branchContains(TokenTypes.LITERAL_STATIC); 380 break; 381 } 382 else { 383 parent = parent.getParent(); 384 } 385 } 386 return inStatic; 387 } 388 389 /** 390 * Decides whether to ignore an AST node that is the parameter of a 391 * setter method, where the property setter method for field 'xyz' has 392 * name 'setXyz', one parameter named 'xyz', and return type void 393 * (default behavior) or return type is name of the class in which 394 * such method is declared (allowed only if 395 * {@link #setSetterCanReturnItsClass(boolean)} is called with 396 * value <em>true</em>) 397 * 398 * @param ast the AST to check. 399 * @param name the name of ast. 400 * @return true if ast should be ignored because check property 401 * ignoreSetter is true and ast is the parameter of a setter method. 402 */ 403 private boolean isIgnoredSetterParam(DetailAST ast, String name) { 404 if (ast.getType() == TokenTypes.PARAMETER_DEF && ignoreSetter) { 405 final DetailAST parametersAST = ast.getParent(); 406 final DetailAST methodAST = parametersAST.getParent(); 407 if (parametersAST.getChildCount() == 1 408 && methodAST.getType() == TokenTypes.METHOD_DEF 409 && isSetterMethod(methodAST, name)) { 410 return true; 411 } 412 } 413 return false; 414 } 415 416 /** 417 * Determine if a specific method identified by methodAST and a single 418 * variable name aName is a setter. This recognition partially depends 419 * on mSetterCanReturnItsClass property. 420 * 421 * @param aMethodAST AST corresponding to a method call 422 * @param aName name of single parameter of this method. 423 * @return true of false indicating of method is a setter or not. 424 */ 425 private boolean isSetterMethod(DetailAST aMethodAST, String aName) { 426 final String methodName = 427 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 428 boolean isSetterMethod = false; 429 430 if (("set" + capitalize(aName)).equals(methodName)) { 431 // method name did match set${Name}(${anyType} ${aName}) 432 // where ${Name} is capitalized version of ${aName} 433 // therefore this method is potentially a setter 434 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 435 final String returnType = typeAST.getFirstChild().getText(); 436 if (typeAST.branchContains(TokenTypes.LITERAL_VOID) 437 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) { 438 // this method has signature 439 // 440 // void set${Name}(${anyType} ${name}) 441 // 442 // and therefore considered to be a setter 443 // 444 // or 445 // 446 // return type is not void, but it is the same as the class 447 // where method is declared and and mSetterCanReturnItsClass 448 // is set to true 449 isSetterMethod = true; 450 } 451 } 452 453 return isSetterMethod; 454 } 455 456 /** 457 * Capitalizes a given property name the way we expect to see it in 458 * a setter name. 459 * @param name a property name 460 * @return capitalized property name 461 */ 462 private static String capitalize(final String name) { 463 String setterName = name; 464 // we should not capitalize the first character if the second 465 // one is a capital one, since according to JavaBeans spec 466 // setXYzz() is a setter for XYzz property, not for xYzz one. 467 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) { 468 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 469 } 470 return setterName; 471 } 472 473 /** 474 * Decides whether to ignore an AST node that is the parameter of a 475 * constructor. 476 * @param ast the AST to check. 477 * @return true if ast should be ignored because check property 478 * ignoreConstructorParameter is true and ast is a constructor parameter. 479 */ 480 private boolean isIgnoredConstructorParam(DetailAST ast) { 481 boolean result = false; 482 if (ast.getType() == TokenTypes.PARAMETER_DEF 483 && ignoreConstructorParameter) { 484 final DetailAST parametersAST = ast.getParent(); 485 final DetailAST constructorAST = parametersAST.getParent(); 486 result = constructorAST.getType() == TokenTypes.CTOR_DEF; 487 } 488 return result; 489 } 490 491 /** 492 * Decides whether to ignore an AST node that is the parameter of an 493 * abstract method. 494 * @param ast the AST to check. 495 * @return true if ast should be ignored because check property 496 * ignoreAbstractMethods is true and ast is a parameter of abstract methods. 497 */ 498 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) { 499 boolean result = false; 500 if (ast.getType() == TokenTypes.PARAMETER_DEF 501 && ignoreAbstractMethods) { 502 final DetailAST method = ast.getParent().getParent(); 503 if (method.getType() == TokenTypes.METHOD_DEF) { 504 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 505 result = mods.branchContains(TokenTypes.ABSTRACT); 506 } 507 } 508 return result; 509 } 510 511 /** 512 * Set the ignore format to the specified regular expression. 513 * @param format a {@code String} value 514 */ 515 public void setIgnoreFormat(String format) { 516 regexp = CommonUtils.createPattern(format); 517 } 518 519 /** 520 * Set whether to ignore the parameter of a property setter method. 521 * @param ignoreSetter decide whether to ignore the parameter of 522 * a property setter method. 523 */ 524 public void setIgnoreSetter(boolean ignoreSetter) { 525 this.ignoreSetter = ignoreSetter; 526 } 527 528 /** 529 * Controls if setter can return only void (default behavior) or it 530 * can also return class in which it is declared. 531 * 532 * @param aSetterCanReturnItsClass if true then setter can return 533 * either void or class in which it is declared. If false then 534 * in order to be recognized as setter method (otherwise 535 * already recognized as a setter) must return void. Later is 536 * the default behavior. 537 */ 538 public void setSetterCanReturnItsClass( 539 boolean aSetterCanReturnItsClass) { 540 setterCanReturnItsClass = aSetterCanReturnItsClass; 541 } 542 543 /** 544 * Set whether to ignore constructor parameters. 545 * @param ignoreConstructorParameter decide whether to ignore 546 * constructor parameters. 547 */ 548 public void setIgnoreConstructorParameter( 549 boolean ignoreConstructorParameter) { 550 this.ignoreConstructorParameter = ignoreConstructorParameter; 551 } 552 553 /** 554 * Set whether to ignore parameters of abstract methods. 555 * @param ignoreAbstractMethods decide whether to ignore 556 * parameters of abstract methods. 557 */ 558 public void setIgnoreAbstractMethods( 559 boolean ignoreAbstractMethods) { 560 this.ignoreAbstractMethods = ignoreAbstractMethods; 561 } 562 563 /** 564 * Holds the names of static and instance fields of a type. 565 * @author Rick Giles 566 */ 567 private static class FieldFrame { 568 /** Name of the frame, such name of the class or enum declaration. */ 569 private final String frameName; 570 571 /** Is this a static inner type. */ 572 private final boolean staticType; 573 574 /** Parent frame. */ 575 private final FieldFrame parent; 576 577 /** Set of instance field names. */ 578 private final Set<String> instanceFields = Sets.newHashSet(); 579 580 /** Set of static field names. */ 581 private final Set<String> staticFields = Sets.newHashSet(); 582 583 /** 584 * Creates new frame. 585 * @param parent parent frame. 586 * @param staticType is this a static inner type (class or enum). 587 * @param frameName name associated with the frame, which can be a 588 */ 589 FieldFrame(FieldFrame parent, boolean staticType, String frameName) { 590 this.parent = parent; 591 this.staticType = staticType; 592 this.frameName = frameName; 593 } 594 595 /** 596 * Adds an instance field to this FieldFrame. 597 * @param field the name of the instance field. 598 */ 599 public void addInstanceField(String field) { 600 instanceFields.add(field); 601 } 602 603 /** 604 * Adds a static field to this FieldFrame. 605 * @param field the name of the instance field. 606 */ 607 public void addStaticField(String field) { 608 staticFields.add(field); 609 } 610 611 /** 612 * Determines whether this FieldFrame contains an instance field. 613 * @param field the field to check. 614 * @return true if this FieldFrame contains instance field field. 615 */ 616 public boolean containsInstanceField(String field) { 617 return instanceFields.contains(field) 618 || parent != null 619 && !staticType 620 && parent.containsInstanceField(field); 621 622 } 623 624 /** 625 * Determines whether this FieldFrame contains a static field. 626 * @param field the field to check. 627 * @return true if this FieldFrame contains static field field. 628 */ 629 public boolean containsStaticField(String field) { 630 return staticFields.contains(field) 631 || parent != null 632 && parent.containsStaticField(field); 633 } 634 635 /** 636 * Getter for parent frame. 637 * @return parent frame. 638 */ 639 public FieldFrame getParent() { 640 return parent; 641 } 642 643 /** 644 * Check if current frame is embedded in class or enum with 645 * specific name. 646 * 647 * @param classOrEnumName name of class or enum that we are looking 648 * for in the chain of field frames. 649 * 650 * @return true if current frame is embedded in class or enum 651 * with name classOrNameName 652 */ 653 private boolean isEmbeddedIn(String classOrEnumName) { 654 FieldFrame currentFrame = this; 655 while (currentFrame != null) { 656 if (Objects.equals(currentFrame.frameName, classOrEnumName)) { 657 return true; 658 } 659 currentFrame = currentFrame.parent; 660 } 661 return false; 662 } 663 } 664}