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.HashSet; 023import java.util.Locale; 024import java.util.Objects; 025import java.util.Set; 026import java.util.regex.Pattern; 027 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.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 AbstractCheck { 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 ignoreFormat; 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 && !CheckUtils.isReceiverParameter(ast) 303 && (ScopeUtils.isLocalVariableDef(ast) 304 || ast.getType() == TokenTypes.PARAMETER_DEF)) { 305 // local variable or parameter. Does it shadow a field? 306 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 307 final String name = nameAST.getText(); 308 309 if ((isStaticFieldHiddenFromAnonymousClass(ast, name) 310 || isStaticOrInstanceField(ast, name)) 311 && !isMatchingRegexp(name) 312 && !isIgnoredParam(ast, name)) { 313 log(nameAST, MSG_KEY, name); 314 } 315 } 316 } 317 318 /** 319 * Checks whether a static field is hidden from closure. 320 * @param nameAST local variable or parameter. 321 * @param name field name. 322 * @return true if static field is hidden from closure. 323 */ 324 private boolean isStaticFieldHiddenFromAnonymousClass(DetailAST nameAST, String name) { 325 return isInStatic(nameAST) 326 && frame.containsStaticField(name); 327 } 328 329 /** 330 * Checks whether method or constructor parameter is ignored. 331 * @param ast the parameter token. 332 * @param name the parameter name. 333 * @return true if parameter is ignored. 334 */ 335 private boolean isIgnoredParam(DetailAST ast, String name) { 336 return isIgnoredSetterParam(ast, name) 337 || isIgnoredConstructorParam(ast) 338 || isIgnoredParamOfAbstractMethod(ast); 339 } 340 341 /** 342 * Check for static or instance field. 343 * @param ast token 344 * @param name identifier of token 345 * @return true if static or instance field 346 */ 347 private boolean isStaticOrInstanceField(DetailAST ast, String name) { 348 return frame.containsStaticField(name) 349 || !isInStatic(ast) && frame.containsInstanceField(name); 350 } 351 352 /** 353 * Check name by regExp. 354 * @param name string value to check 355 * @return true is regexp is matching 356 */ 357 private boolean isMatchingRegexp(String name) { 358 return ignoreFormat != null && ignoreFormat.matcher(name).find(); 359 } 360 361 /** 362 * Determines whether an AST node is in a static method or static 363 * initializer. 364 * @param ast the node to check. 365 * @return true if ast is in a static method or a static block; 366 */ 367 private static boolean isInStatic(DetailAST ast) { 368 DetailAST parent = ast.getParent(); 369 boolean inStatic = false; 370 371 while (parent != null && !inStatic) { 372 if (parent.getType() == TokenTypes.STATIC_INIT) { 373 inStatic = true; 374 } 375 else if (parent.getType() == TokenTypes.METHOD_DEF 376 && !ScopeUtils.isInScope(parent, Scope.ANONINNER) 377 || parent.getType() == TokenTypes.VARIABLE_DEF) { 378 final DetailAST mods = 379 parent.findFirstToken(TokenTypes.MODIFIERS); 380 inStatic = mods.branchContains(TokenTypes.LITERAL_STATIC); 381 break; 382 } 383 else { 384 parent = parent.getParent(); 385 } 386 } 387 return inStatic; 388 } 389 390 /** 391 * Decides whether to ignore an AST node that is the parameter of a 392 * setter method, where the property setter method for field 'xyz' has 393 * name 'setXyz', one parameter named 'xyz', and return type void 394 * (default behavior) or return type is name of the class in which 395 * such method is declared (allowed only if 396 * {@link #setSetterCanReturnItsClass(boolean)} is called with 397 * value <em>true</em>). 398 * 399 * @param ast the AST to check. 400 * @param name the name of ast. 401 * @return true if ast should be ignored because check property 402 * ignoreSetter is true and ast is the parameter of a setter method. 403 */ 404 private boolean isIgnoredSetterParam(DetailAST ast, String name) { 405 if (ignoreSetter && ast.getType() == TokenTypes.PARAMETER_DEF) { 406 final DetailAST parametersAST = ast.getParent(); 407 final DetailAST methodAST = parametersAST.getParent(); 408 if (parametersAST.getChildCount() == 1 409 && methodAST.getType() == TokenTypes.METHOD_DEF 410 && isSetterMethod(methodAST, name)) { 411 return true; 412 } 413 } 414 return false; 415 } 416 417 /** 418 * Determine if a specific method identified by methodAST and a single 419 * variable name aName is a setter. This recognition partially depends 420 * on mSetterCanReturnItsClass property. 421 * 422 * @param aMethodAST AST corresponding to a method call 423 * @param aName name of single parameter of this method. 424 * @return true of false indicating of method is a setter or not. 425 */ 426 private boolean isSetterMethod(DetailAST aMethodAST, String aName) { 427 final String methodName = 428 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 429 boolean isSetterMethod = false; 430 431 if (("set" + capitalize(aName)).equals(methodName)) { 432 // method name did match set${Name}(${anyType} ${aName}) 433 // where ${Name} is capitalized version of ${aName} 434 // therefore this method is potentially a setter 435 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 436 final String returnType = typeAST.getFirstChild().getText(); 437 if (typeAST.branchContains(TokenTypes.LITERAL_VOID) 438 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) { 439 // this method has signature 440 // 441 // void set${Name}(${anyType} ${name}) 442 // 443 // and therefore considered to be a setter 444 // 445 // or 446 // 447 // return type is not void, but it is the same as the class 448 // where method is declared and and mSetterCanReturnItsClass 449 // is set to true 450 isSetterMethod = true; 451 } 452 } 453 454 return isSetterMethod; 455 } 456 457 /** 458 * Capitalizes a given property name the way we expect to see it in 459 * a setter name. 460 * @param name a property name 461 * @return capitalized property name 462 */ 463 private static String capitalize(final String name) { 464 String setterName = name; 465 // we should not capitalize the first character if the second 466 // one is a capital one, since according to JavaBeans spec 467 // setXYzz() is a setter for XYzz property, not for xYzz one. 468 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) { 469 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 470 } 471 return setterName; 472 } 473 474 /** 475 * Decides whether to ignore an AST node that is the parameter of a 476 * constructor. 477 * @param ast the AST to check. 478 * @return true if ast should be ignored because check property 479 * ignoreConstructorParameter is true and ast is a constructor parameter. 480 */ 481 private boolean isIgnoredConstructorParam(DetailAST ast) { 482 boolean result = false; 483 if (ignoreConstructorParameter 484 && ast.getType() == TokenTypes.PARAMETER_DEF) { 485 final DetailAST parametersAST = ast.getParent(); 486 final DetailAST constructorAST = parametersAST.getParent(); 487 result = constructorAST.getType() == TokenTypes.CTOR_DEF; 488 } 489 return result; 490 } 491 492 /** 493 * Decides whether to ignore an AST node that is the parameter of an 494 * abstract method. 495 * @param ast the AST to check. 496 * @return true if ast should be ignored because check property 497 * ignoreAbstractMethods is true and ast is a parameter of abstract methods. 498 */ 499 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) { 500 boolean result = false; 501 if (ignoreAbstractMethods 502 && ast.getType() == TokenTypes.PARAMETER_DEF) { 503 final DetailAST method = ast.getParent().getParent(); 504 if (method.getType() == TokenTypes.METHOD_DEF) { 505 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 506 result = mods.branchContains(TokenTypes.ABSTRACT); 507 } 508 } 509 return result; 510 } 511 512 /** 513 * Set the ignore format for the specified regular expression. 514 * @param pattern a pattern. 515 */ 516 public void setIgnoreFormat(Pattern pattern) { 517 ignoreFormat = pattern; 518 } 519 520 /** 521 * Set whether to ignore the parameter of a property setter method. 522 * @param ignoreSetter decide whether to ignore the parameter of 523 * a property setter method. 524 */ 525 public void setIgnoreSetter(boolean ignoreSetter) { 526 this.ignoreSetter = ignoreSetter; 527 } 528 529 /** 530 * Controls if setter can return only void (default behavior) or it 531 * can also return class in which it is declared. 532 * 533 * @param aSetterCanReturnItsClass if true then setter can return 534 * either void or class in which it is declared. If false then 535 * in order to be recognized as setter method (otherwise 536 * already recognized as a setter) must return void. Later is 537 * the default behavior. 538 */ 539 public void setSetterCanReturnItsClass( 540 boolean aSetterCanReturnItsClass) { 541 setterCanReturnItsClass = aSetterCanReturnItsClass; 542 } 543 544 /** 545 * Set whether to ignore constructor parameters. 546 * @param ignoreConstructorParameter decide whether to ignore 547 * constructor parameters. 548 */ 549 public void setIgnoreConstructorParameter( 550 boolean ignoreConstructorParameter) { 551 this.ignoreConstructorParameter = ignoreConstructorParameter; 552 } 553 554 /** 555 * Set whether to ignore parameters of abstract methods. 556 * @param ignoreAbstractMethods decide whether to ignore 557 * parameters of abstract methods. 558 */ 559 public void setIgnoreAbstractMethods( 560 boolean ignoreAbstractMethods) { 561 this.ignoreAbstractMethods = ignoreAbstractMethods; 562 } 563 564 /** 565 * Holds the names of static and instance fields of a type. 566 * @author Rick Giles 567 */ 568 private static class FieldFrame { 569 /** Name of the frame, such name of the class or enum declaration. */ 570 private final String frameName; 571 572 /** Is this a static inner type. */ 573 private final boolean staticType; 574 575 /** Parent frame. */ 576 private final FieldFrame parent; 577 578 /** Set of instance field names. */ 579 private final Set<String> instanceFields = new HashSet<>(); 580 581 /** Set of static field names. */ 582 private final Set<String> staticFields = new HashSet<>(); 583 584 /** 585 * Creates new frame. 586 * @param parent parent frame. 587 * @param staticType is this a static inner type (class or enum). 588 * @param frameName name associated with the frame, which can be a 589 */ 590 FieldFrame(FieldFrame parent, boolean staticType, String frameName) { 591 this.parent = parent; 592 this.staticType = staticType; 593 this.frameName = frameName; 594 } 595 596 /** 597 * Adds an instance field to this FieldFrame. 598 * @param field the name of the instance field. 599 */ 600 public void addInstanceField(String field) { 601 instanceFields.add(field); 602 } 603 604 /** 605 * Adds a static field to this FieldFrame. 606 * @param field the name of the instance field. 607 */ 608 public void addStaticField(String field) { 609 staticFields.add(field); 610 } 611 612 /** 613 * Determines whether this FieldFrame contains an instance field. 614 * @param field the field to check. 615 * @return true if this FieldFrame contains instance field field. 616 */ 617 public boolean containsInstanceField(String field) { 618 return instanceFields.contains(field) 619 || parent != null 620 && !staticType 621 && parent.containsInstanceField(field); 622 623 } 624 625 /** 626 * Determines whether this FieldFrame contains a static field. 627 * @param field the field to check. 628 * @return true if this FieldFrame contains static field field. 629 */ 630 public boolean containsStaticField(String field) { 631 return staticFields.contains(field) 632 || parent != null 633 && parent.containsStaticField(field); 634 } 635 636 /** 637 * Getter for parent frame. 638 * @return parent frame. 639 */ 640 public FieldFrame getParent() { 641 return parent; 642 } 643 644 /** 645 * Check if current frame is embedded in class or enum with 646 * specific name. 647 * 648 * @param classOrEnumName name of class or enum that we are looking 649 * for in the chain of field frames. 650 * 651 * @return true if current frame is embedded in class or enum 652 * with name classOrNameName 653 */ 654 private boolean isEmbeddedIn(String classOrEnumName) { 655 FieldFrame currentFrame = this; 656 while (currentFrame != null) { 657 if (Objects.equals(currentFrame.frameName, classOrEnumName)) { 658 return true; 659 } 660 currentFrame = currentFrame.parent; 661 } 662 return false; 663 } 664 } 665}