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