001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.ArrayDeque; 023import java.util.BitSet; 024import java.util.Deque; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.LinkedList; 028import java.util.Map; 029import java.util.Queue; 030import java.util.Set; 031 032import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 033import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 034import com.puppycrawl.tools.checkstyle.api.DetailAST; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 037import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 038import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 039 040/** 041 * <p> 042 * Checks that references to instance variables and methods of the present 043 * object are explicitly of the form "this.varName" or "this.methodName(args)" 044 * and that those references don't rely on the default behavior when "this." is absent. 045 * </p> 046 * <p>Warning: the Check is very controversial if 'validateOnlyOverlapping' option is set to 'false' 047 * and not that actual nowadays.</p> 048 * <p>Rationale:</p> 049 * <ol> 050 * <li> 051 * The same notation/habit for C++ and Java (C++ have global methods, so having 052 * "this." do make sense in it to distinguish call of method of class 053 * instead of global). 054 * </li> 055 * <li> 056 * Non-IDE development (ease of refactoring, some clearness to distinguish 057 * static and non-static methods). 058 * </li> 059 * </ol> 060 * <p>Limitations: Nothing is currently done about static variables 061 * or catch-blocks. Static methods invoked on a class name seem to be OK; 062 * both the class name and the method name have a DOT parent. 063 * Non-static methods invoked on either this or a variable name seem to be 064 * OK, likewise. 065 * </p> 066 * <ul> 067 * <li> 068 * Property {@code checkFields} - Control whether to check references to fields. 069 * Type is {@code boolean}. 070 * Default value is {@code true}. 071 * </li> 072 * <li> 073 * Property {@code checkMethods} - Control whether to check references to methods. 074 * Type is {@code boolean}. 075 * Default value is {@code true}. 076 * </li> 077 * <li> 078 * Property {@code validateOnlyOverlapping} - Control whether to check only 079 * overlapping by variables or arguments. 080 * Type is {@code boolean}. 081 * Default value is {@code true}. 082 * </li> 083 * </ul> 084 * <p> 085 * To configure the default check: 086 * </p> 087 * <pre> 088 * <module name="RequireThis"/> 089 * </pre> 090 * <p>Example:</p> 091 * <pre> 092 * public class Test { 093 * private int a; 094 * private int b; 095 * private int c; 096 * 097 * public Test(int a) { 098 * // overlapping by constructor argument 099 * this.a = a; // OK, this keyword used 100 * b = 0; // OK, no overlap 101 * foo(5); // OK 102 * } 103 * 104 * public void foo(int c) { 105 * // overlapping by method argument 106 * c = c; // violation, reference to instance variable "c" requires "this" 107 * } 108 * } 109 * </pre> 110 * <p> 111 * To configure the check for fields only: 112 * </p> 113 * <pre> 114 * <module name="RequireThis"> 115 * <property name="checkMethods" value="false"/> 116 * </module> 117 * </pre> 118 * <p>Example:</p> 119 * <pre> 120 * public class Test { 121 * private int a; 122 * private int b; 123 * private int c; 124 * 125 * public Test(int a) { 126 * // overlapping by constructor argument 127 * this.a = a; // OK, this keyword used 128 * b = 0; // OK, no overlap 129 * foo(5); // OK, no validation for methods 130 * } 131 * 132 * public void foo(int c) { 133 * // overlapping by method argument 134 * c = c; // violation, reference to instance variable "c" requires "this" 135 * } 136 * } 137 * </pre> 138 * <p> 139 * To configure the check for methods only: 140 * </p> 141 * <pre> 142 * <module name="RequireThis"> 143 * <property name="checkFields" value="false"/> 144 * </module> 145 * </pre> 146 * <p>Example:</p> 147 * <pre> 148 * public class Test { 149 * private int a; 150 * private int b; 151 * private int c; 152 * 153 * public Test(int a) { 154 * // overlapping by constructor argument 155 * this.a = a; // OK, no validation for fields 156 * b = 0; // OK, no validation for fields 157 * foo(5); // OK, no overlap 158 * } 159 * 160 * public void foo(int c) { 161 * // overlapping by method argument 162 * c = c; // OK, no validation for fields 163 * } 164 * } 165 * </pre> 166 * <p> 167 * Note that method call foo(5) does not raise a violation 168 * because methods cannot be overlapped in java. 169 * </p> 170 * <p> 171 * To configure the check to validate for non-overlapping fields and methods: 172 * </p> 173 * <pre> 174 * <module name="RequireThis"> 175 * <property name="validateOnlyOverlapping" value="false"/> 176 * </module> 177 * </pre> 178 * <p>Example:</p> 179 * <pre> 180 * public class Test { 181 * private int a; 182 * private int b; 183 * private int c; 184 * 185 * public Test(int a) { 186 * // overlapping by constructor argument 187 * this.a = a; // OK, no validation for fields 188 * b = 0; // violation, reference to instance variable "b" requires "this" 189 * foo(5); // violation, method call "foo(5)" requires "this" 190 * } 191 * 192 * public void foo(int c) { 193 * // overlapping by method argument 194 * c = c; // violation, reference to instance variable "c" requires "this" 195 * } 196 * } 197 * </pre> 198 * <p> 199 * Please, be aware of the following logic, which is implemented in the check: 200 * </p> 201 * <p> 202 * 1) If you arrange 'this' in your code on your own, the check will not raise violation for 203 * variables which use 'this' to reference a class field, for example: 204 * </p> 205 * <pre> 206 * public class C { 207 * private int scale; 208 * private int x; 209 * 210 * public void foo(int scale) { 211 * scale = this.scale; // no violation 212 * 213 * if (scale > 0) { 214 * scale = -scale; // no violation 215 * } 216 * x *= scale; 217 * } 218 * } 219 * </pre> 220 * <p> 221 * 2) If method parameter is returned from the method, the check will not raise violation for 222 * returned variable/parameter, for example: 223 * </p> 224 * <pre> 225 * public class D { 226 * private String prefix; 227 * 228 * public String modifyPrefix(String prefix) { 229 * prefix = "^" + prefix + "$"; // no violation, because method parameter is returned 230 * return prefix; 231 * } 232 * } 233 * </pre> 234 * <p> 235 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 236 * </p> 237 * <p> 238 * Violation Message Keys: 239 * </p> 240 * <ul> 241 * <li> 242 * {@code require.this.method} 243 * </li> 244 * <li> 245 * {@code require.this.variable} 246 * </li> 247 * </ul> 248 * 249 * @since 3.4 250 */ 251// -@cs[ClassDataAbstractionCoupling] This check requires to work with and identify many frames. 252@FileStatefulCheck 253public class RequireThisCheck extends AbstractCheck { 254 255 /** 256 * A key is pointing to the warning message text in "messages.properties" 257 * file. 258 */ 259 public static final String MSG_METHOD = "require.this.method"; 260 /** 261 * A key is pointing to the warning message text in "messages.properties" 262 * file. 263 */ 264 public static final String MSG_VARIABLE = "require.this.variable"; 265 266 /** Set of all declaration tokens. */ 267 private static final BitSet DECLARATION_TOKENS = TokenUtil.asBitSet( 268 TokenTypes.VARIABLE_DEF, 269 TokenTypes.CTOR_DEF, 270 TokenTypes.METHOD_DEF, 271 TokenTypes.CLASS_DEF, 272 TokenTypes.ENUM_DEF, 273 TokenTypes.ANNOTATION_DEF, 274 TokenTypes.INTERFACE_DEF, 275 TokenTypes.PARAMETER_DEF, 276 TokenTypes.TYPE_ARGUMENT, 277 TokenTypes.RECORD_DEF, 278 TokenTypes.RECORD_COMPONENT_DEF, 279 TokenTypes.RESOURCE 280 ); 281 /** Set of all assign tokens. */ 282 private static final BitSet ASSIGN_TOKENS = TokenUtil.asBitSet( 283 TokenTypes.ASSIGN, 284 TokenTypes.PLUS_ASSIGN, 285 TokenTypes.STAR_ASSIGN, 286 TokenTypes.DIV_ASSIGN, 287 TokenTypes.MOD_ASSIGN, 288 TokenTypes.SR_ASSIGN, 289 TokenTypes.BSR_ASSIGN, 290 TokenTypes.SL_ASSIGN, 291 TokenTypes.BAND_ASSIGN, 292 TokenTypes.BXOR_ASSIGN 293 ); 294 /** Set of all compound assign tokens. */ 295 private static final BitSet COMPOUND_ASSIGN_TOKENS = TokenUtil.asBitSet( 296 TokenTypes.PLUS_ASSIGN, 297 TokenTypes.STAR_ASSIGN, 298 TokenTypes.DIV_ASSIGN, 299 TokenTypes.MOD_ASSIGN, 300 TokenTypes.SR_ASSIGN, 301 TokenTypes.BSR_ASSIGN, 302 TokenTypes.SL_ASSIGN, 303 TokenTypes.BAND_ASSIGN, 304 TokenTypes.BXOR_ASSIGN 305 ); 306 307 /** Frame for the currently processed AST. */ 308 private final Deque<AbstractFrame> current = new ArrayDeque<>(); 309 310 /** Tree of all the parsed frames. */ 311 private Map<DetailAST, AbstractFrame> frames; 312 313 /** Control whether to check references to fields. */ 314 private boolean checkFields = true; 315 /** Control whether to check references to methods. */ 316 private boolean checkMethods = true; 317 /** Control whether to check only overlapping by variables or arguments. */ 318 private boolean validateOnlyOverlapping = true; 319 320 /** 321 * Setter to control whether to check references to fields. 322 * 323 * @param checkFields should we check fields usage or not 324 */ 325 public void setCheckFields(boolean checkFields) { 326 this.checkFields = checkFields; 327 } 328 329 /** 330 * Setter to control whether to check references to methods. 331 * 332 * @param checkMethods should we check methods usage or not 333 */ 334 public void setCheckMethods(boolean checkMethods) { 335 this.checkMethods = checkMethods; 336 } 337 338 /** 339 * Setter to control whether to check only overlapping by variables or arguments. 340 * 341 * @param validateOnlyOverlapping should we check only overlapping by variables or arguments 342 */ 343 public void setValidateOnlyOverlapping(boolean validateOnlyOverlapping) { 344 this.validateOnlyOverlapping = validateOnlyOverlapping; 345 } 346 347 @Override 348 public int[] getDefaultTokens() { 349 return getRequiredTokens(); 350 } 351 352 @Override 353 public int[] getRequiredTokens() { 354 return new int[] { 355 TokenTypes.CLASS_DEF, 356 TokenTypes.INTERFACE_DEF, 357 TokenTypes.ENUM_DEF, 358 TokenTypes.ANNOTATION_DEF, 359 TokenTypes.CTOR_DEF, 360 TokenTypes.METHOD_DEF, 361 TokenTypes.LITERAL_FOR, 362 TokenTypes.SLIST, 363 TokenTypes.IDENT, 364 TokenTypes.RECORD_DEF, 365 TokenTypes.COMPACT_CTOR_DEF, 366 TokenTypes.LITERAL_TRY, 367 TokenTypes.RESOURCE, 368 }; 369 } 370 371 @Override 372 public int[] getAcceptableTokens() { 373 return getRequiredTokens(); 374 } 375 376 @Override 377 public void beginTree(DetailAST rootAST) { 378 frames = new HashMap<>(); 379 current.clear(); 380 381 final Deque<AbstractFrame> frameStack = new LinkedList<>(); 382 DetailAST curNode = rootAST; 383 while (curNode != null) { 384 collectDeclarations(frameStack, curNode); 385 DetailAST toVisit = curNode.getFirstChild(); 386 while (curNode != null && toVisit == null) { 387 endCollectingDeclarations(frameStack, curNode); 388 toVisit = curNode.getNextSibling(); 389 curNode = curNode.getParent(); 390 } 391 curNode = toVisit; 392 } 393 } 394 395 @Override 396 public void visitToken(DetailAST ast) { 397 switch (ast.getType()) { 398 case TokenTypes.IDENT: 399 processIdent(ast); 400 break; 401 case TokenTypes.CLASS_DEF: 402 case TokenTypes.INTERFACE_DEF: 403 case TokenTypes.ENUM_DEF: 404 case TokenTypes.ANNOTATION_DEF: 405 case TokenTypes.SLIST: 406 case TokenTypes.METHOD_DEF: 407 case TokenTypes.CTOR_DEF: 408 case TokenTypes.LITERAL_FOR: 409 case TokenTypes.RECORD_DEF: 410 current.push(frames.get(ast)); 411 break; 412 case TokenTypes.LITERAL_TRY: 413 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 414 current.push(frames.get(ast)); 415 } 416 break; 417 default: 418 break; 419 } 420 } 421 422 @Override 423 public void leaveToken(DetailAST ast) { 424 switch (ast.getType()) { 425 case TokenTypes.CLASS_DEF: 426 case TokenTypes.INTERFACE_DEF: 427 case TokenTypes.ENUM_DEF: 428 case TokenTypes.ANNOTATION_DEF: 429 case TokenTypes.SLIST: 430 case TokenTypes.METHOD_DEF: 431 case TokenTypes.CTOR_DEF: 432 case TokenTypes.LITERAL_FOR: 433 case TokenTypes.RECORD_DEF: 434 current.pop(); 435 break; 436 case TokenTypes.LITERAL_TRY: 437 if (current.peek().getType() == FrameType.TRY_WITH_RESOURCES_FRAME) { 438 current.pop(); 439 } 440 break; 441 default: 442 break; 443 } 444 } 445 446 /** 447 * Checks if a given IDENT is method call or field name which 448 * requires explicit {@code this} qualifier. 449 * 450 * @param ast IDENT to check. 451 */ 452 private void processIdent(DetailAST ast) { 453 int parentType = ast.getParent().getType(); 454 if (parentType == TokenTypes.EXPR 455 && ast.getParent().getParent().getParent().getType() 456 == TokenTypes.ANNOTATION_FIELD_DEF) { 457 parentType = TokenTypes.ANNOTATION_FIELD_DEF; 458 } 459 switch (parentType) { 460 case TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR: 461 case TokenTypes.ANNOTATION: 462 case TokenTypes.ANNOTATION_FIELD_DEF: 463 // no need to check annotations content 464 break; 465 case TokenTypes.METHOD_CALL: 466 if (checkMethods) { 467 final AbstractFrame frame = getMethodWithoutThis(ast); 468 if (frame != null) { 469 logViolation(MSG_METHOD, ast, frame); 470 } 471 } 472 break; 473 default: 474 if (checkFields) { 475 final AbstractFrame frame = getFieldWithoutThis(ast, parentType); 476 final boolean canUseThis = !isInCompactConstructor(ast); 477 if (frame != null && canUseThis) { 478 logViolation(MSG_VARIABLE, ast, frame); 479 } 480 } 481 break; 482 } 483 } 484 485 /** 486 * Helper method to log a Violation. 487 * 488 * @param msgKey key to locale message format. 489 * @param ast a node to get line id column numbers associated with the message. 490 * @param frame the class frame where the violation is found. 491 */ 492 private void logViolation(String msgKey, DetailAST ast, AbstractFrame frame) { 493 if (frame.getFrameName().equals(getNearestClassFrameName())) { 494 log(ast, msgKey, ast.getText(), ""); 495 } 496 else if (!(frame instanceof AnonymousClassFrame)) { 497 log(ast, msgKey, ast.getText(), frame.getFrameName() + '.'); 498 } 499 } 500 501 /** 502 * Returns the frame where the field is declared, if the given field is used without 503 * 'this', and null otherwise. 504 * 505 * @param ast field definition ast token. 506 * @param parentType type of the parent. 507 * @return the frame where the field is declared, if the given field is used without 508 * 'this' and null otherwise. 509 */ 510 private AbstractFrame getFieldWithoutThis(DetailAST ast, int parentType) { 511 final boolean importOrPackage = ScopeUtil.getSurroundingScope(ast) == null; 512 final boolean typeName = parentType == TokenTypes.TYPE 513 || parentType == TokenTypes.LITERAL_NEW; 514 AbstractFrame frame = null; 515 516 if (!importOrPackage 517 && !typeName 518 && !isDeclarationToken(parentType) 519 && !isLambdaParameter(ast)) { 520 final AbstractFrame fieldFrame = findClassFrame(ast, false); 521 522 if (fieldFrame != null && ((ClassFrame) fieldFrame).hasInstanceMember(ast)) { 523 frame = getClassFrameWhereViolationIsFound(ast); 524 } 525 } 526 return frame; 527 } 528 529 /** 530 * Return whether ast is in a COMPACT_CTOR_DEF. 531 * 532 * @param ast The token to check 533 * @return true if ast is in a COMPACT_CTOR_DEF, false otherwise 534 */ 535 private static boolean isInCompactConstructor(DetailAST ast) { 536 boolean isInCompactCtor = false; 537 DetailAST parent = ast; 538 while (parent != null) { 539 if (parent.getType() == TokenTypes.COMPACT_CTOR_DEF) { 540 isInCompactCtor = true; 541 break; 542 } 543 parent = parent.getParent(); 544 } 545 return isInCompactCtor; 546 } 547 548 /** 549 * Parses the next AST for declarations. 550 * 551 * @param frameStack stack containing the FrameTree being built. 552 * @param ast AST to parse. 553 */ 554 // -@cs[JavaNCSS] This method is a big switch and is too hard to remove. 555 private static void collectDeclarations(Deque<AbstractFrame> frameStack, DetailAST ast) { 556 final AbstractFrame frame = frameStack.peek(); 557 switch (ast.getType()) { 558 case TokenTypes.VARIABLE_DEF: 559 collectVariableDeclarations(ast, frame); 560 break; 561 case TokenTypes.RECORD_COMPONENT_DEF: 562 final DetailAST componentIdent = ast.findFirstToken(TokenTypes.IDENT); 563 ((ClassFrame) frame).addInstanceMember(componentIdent); 564 break; 565 case TokenTypes.PARAMETER_DEF: 566 if (!CheckUtil.isReceiverParameter(ast) 567 && !isLambdaParameter(ast)) { 568 final DetailAST parameterIdent = ast.findFirstToken(TokenTypes.IDENT); 569 frame.addIdent(parameterIdent); 570 } 571 break; 572 case TokenTypes.RESOURCE: 573 final DetailAST resourceIdent = ast.findFirstToken(TokenTypes.IDENT); 574 if (resourceIdent != null) { 575 frame.addIdent(resourceIdent); 576 } 577 break; 578 case TokenTypes.CLASS_DEF: 579 case TokenTypes.INTERFACE_DEF: 580 case TokenTypes.ENUM_DEF: 581 case TokenTypes.ANNOTATION_DEF: 582 case TokenTypes.RECORD_DEF: 583 final DetailAST classFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 584 frameStack.addFirst(new ClassFrame(frame, classFrameNameIdent)); 585 break; 586 case TokenTypes.SLIST: 587 frameStack.addFirst(new BlockFrame(frame, ast)); 588 break; 589 case TokenTypes.METHOD_DEF: 590 collectMethodDeclarations(frameStack, ast, frame); 591 break; 592 case TokenTypes.CTOR_DEF: 593 case TokenTypes.COMPACT_CTOR_DEF: 594 final DetailAST ctorFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 595 frameStack.addFirst(new ConstructorFrame(frame, ctorFrameNameIdent)); 596 break; 597 case TokenTypes.ENUM_CONSTANT_DEF: 598 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 599 ((ClassFrame) frame).addStaticMember(ident); 600 break; 601 case TokenTypes.LITERAL_CATCH: 602 final AbstractFrame catchFrame = new CatchFrame(frame, ast); 603 frameStack.addFirst(catchFrame); 604 break; 605 case TokenTypes.LITERAL_FOR: 606 final AbstractFrame forFrame = new ForFrame(frame, ast); 607 frameStack.addFirst(forFrame); 608 break; 609 case TokenTypes.LITERAL_NEW: 610 if (isAnonymousClassDef(ast)) { 611 frameStack.addFirst(new AnonymousClassFrame(frame, 612 ast.toString())); 613 } 614 break; 615 case TokenTypes.LITERAL_TRY: 616 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 617 frameStack.addFirst(new TryWithResourcesFrame(frame, ast)); 618 } 619 break; 620 default: 621 // do nothing 622 } 623 } 624 625 /** 626 * Collects variable declarations. 627 * 628 * @param ast variable token. 629 * @param frame current frame. 630 */ 631 private static void collectVariableDeclarations(DetailAST ast, AbstractFrame frame) { 632 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 633 if (frame.getType() == FrameType.CLASS_FRAME) { 634 final DetailAST mods = 635 ast.findFirstToken(TokenTypes.MODIFIERS); 636 if (ScopeUtil.isInInterfaceBlock(ast) 637 || mods.findFirstToken(TokenTypes.LITERAL_STATIC) != null) { 638 ((ClassFrame) frame).addStaticMember(ident); 639 } 640 else { 641 ((ClassFrame) frame).addInstanceMember(ident); 642 } 643 } 644 else { 645 frame.addIdent(ident); 646 } 647 } 648 649 /** 650 * Collects {@code METHOD_DEF} declarations. 651 * 652 * @param frameStack stack containing the FrameTree being built. 653 * @param ast AST to parse. 654 * @param frame current frame. 655 */ 656 private static void collectMethodDeclarations(Deque<AbstractFrame> frameStack, 657 DetailAST ast, AbstractFrame frame) { 658 final DetailAST methodFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 659 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 660 if (mods.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 661 ((ClassFrame) frame).addInstanceMethod(methodFrameNameIdent); 662 } 663 else { 664 ((ClassFrame) frame).addStaticMethod(methodFrameNameIdent); 665 } 666 frameStack.addFirst(new MethodFrame(frame, methodFrameNameIdent)); 667 } 668 669 /** 670 * Ends parsing of the AST for declarations. 671 * 672 * @param frameStack Stack containing the FrameTree being built. 673 * @param ast AST that was parsed. 674 */ 675 private void endCollectingDeclarations(Queue<AbstractFrame> frameStack, DetailAST ast) { 676 switch (ast.getType()) { 677 case TokenTypes.CLASS_DEF: 678 case TokenTypes.INTERFACE_DEF: 679 case TokenTypes.ENUM_DEF: 680 case TokenTypes.ANNOTATION_DEF: 681 case TokenTypes.SLIST: 682 case TokenTypes.METHOD_DEF: 683 case TokenTypes.CTOR_DEF: 684 case TokenTypes.LITERAL_CATCH: 685 case TokenTypes.LITERAL_FOR: 686 case TokenTypes.RECORD_DEF: 687 case TokenTypes.COMPACT_CTOR_DEF: 688 frames.put(ast, frameStack.poll()); 689 break; 690 case TokenTypes.LITERAL_NEW: 691 if (isAnonymousClassDef(ast)) { 692 frameStack.remove(); 693 } 694 break; 695 case TokenTypes.LITERAL_TRY: 696 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 697 frames.put(ast, frameStack.poll()); 698 } 699 break; 700 default: 701 // do nothing 702 } 703 } 704 705 /** 706 * Whether the AST is a definition of an anonymous class. 707 * 708 * @param ast the AST to process. 709 * @return true if the AST is a definition of an anonymous class. 710 */ 711 private static boolean isAnonymousClassDef(DetailAST ast) { 712 final DetailAST lastChild = ast.getLastChild(); 713 return lastChild != null 714 && lastChild.getType() == TokenTypes.OBJBLOCK; 715 } 716 717 /** 718 * Returns the class frame where violation is found (where the field is used without 'this') 719 * or null otherwise. 720 * 721 * @param ast IDENT ast to check. 722 * @return the class frame where violation is found or null otherwise. 723 */ 724 // -@cs[CyclomaticComplexity] Method already invokes too many methods that fully explain 725 // a logic, additional abstraction will not make logic/algorithm more readable. 726 private AbstractFrame getClassFrameWhereViolationIsFound(DetailAST ast) { 727 AbstractFrame frameWhereViolationIsFound = null; 728 final AbstractFrame variableDeclarationFrame = findFrame(ast, false); 729 final FrameType variableDeclarationFrameType = variableDeclarationFrame.getType(); 730 final DetailAST prevSibling = ast.getPreviousSibling(); 731 if (variableDeclarationFrameType == FrameType.CLASS_FRAME 732 && !validateOnlyOverlapping 733 && (prevSibling == null || !isInExpression(ast)) 734 && canBeReferencedFromStaticContext(ast)) { 735 frameWhereViolationIsFound = variableDeclarationFrame; 736 } 737 else if (variableDeclarationFrameType == FrameType.METHOD_FRAME) { 738 if (isOverlappingByArgument(ast)) { 739 if (!isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 740 && !isReturnedVariable(variableDeclarationFrame, ast) 741 && canBeReferencedFromStaticContext(ast) 742 && canAssignValueToClassField(ast)) { 743 frameWhereViolationIsFound = findFrame(ast, true); 744 } 745 } 746 else if (!validateOnlyOverlapping 747 && prevSibling == null 748 && isAssignToken(ast.getParent().getType()) 749 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 750 && canBeReferencedFromStaticContext(ast) 751 && canAssignValueToClassField(ast)) { 752 frameWhereViolationIsFound = findFrame(ast, true); 753 } 754 } 755 else if (variableDeclarationFrameType == FrameType.CTOR_FRAME 756 && isOverlappingByArgument(ast) 757 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast)) { 758 frameWhereViolationIsFound = findFrame(ast, true); 759 } 760 else if (variableDeclarationFrameType == FrameType.BLOCK_FRAME 761 && isOverlappingByLocalVariable(ast) 762 && canAssignValueToClassField(ast) 763 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 764 && !isReturnedVariable(variableDeclarationFrame, ast) 765 && canBeReferencedFromStaticContext(ast)) { 766 frameWhereViolationIsFound = findFrame(ast, true); 767 } 768 return frameWhereViolationIsFound; 769 } 770 771 /** 772 * Checks ast parent is in expression. 773 * 774 * @param ast token to check 775 * @return true if token is part of expression, false otherwise 776 */ 777 private static boolean isInExpression(DetailAST ast) { 778 return TokenTypes.DOT == ast.getParent().getType() 779 || TokenTypes.METHOD_REF == ast.getParent().getType(); 780 } 781 782 /** 783 * Checks whether user arranges 'this' for variable in method, constructor, or block on his own. 784 * 785 * @param currentFrame current frame. 786 * @param ident ident token. 787 * @return true if user arranges 'this' for variable in method, constructor, 788 * or block on his own. 789 */ 790 private static boolean isUserDefinedArrangementOfThis(AbstractFrame currentFrame, 791 DetailAST ident) { 792 final DetailAST blockFrameNameIdent = currentFrame.getFrameNameIdent(); 793 final DetailAST definitionToken = blockFrameNameIdent.getParent(); 794 final DetailAST blockStartToken = definitionToken.findFirstToken(TokenTypes.SLIST); 795 final DetailAST blockEndToken = getBlockEndToken(blockFrameNameIdent, blockStartToken); 796 797 boolean userDefinedArrangementOfThis = false; 798 799 final Set<DetailAST> variableUsagesInsideBlock = 800 getAllTokensWhichAreEqualToCurrent(definitionToken, ident, 801 blockEndToken.getLineNo()); 802 803 for (DetailAST variableUsage : variableUsagesInsideBlock) { 804 final DetailAST prevSibling = variableUsage.getPreviousSibling(); 805 if (prevSibling != null 806 && prevSibling.getType() == TokenTypes.LITERAL_THIS) { 807 userDefinedArrangementOfThis = true; 808 break; 809 } 810 } 811 return userDefinedArrangementOfThis; 812 } 813 814 /** 815 * Returns the token which ends the code block. 816 * 817 * @param blockNameIdent block name identifier. 818 * @param blockStartToken token which starts the block. 819 * @return the token which ends the code block. 820 */ 821 private static DetailAST getBlockEndToken(DetailAST blockNameIdent, DetailAST blockStartToken) { 822 DetailAST blockEndToken = null; 823 final DetailAST blockNameIdentParent = blockNameIdent.getParent(); 824 if (blockNameIdentParent.getType() == TokenTypes.CASE_GROUP) { 825 blockEndToken = blockNameIdentParent.getNextSibling(); 826 } 827 else { 828 final Set<DetailAST> rcurlyTokens = getAllTokensOfType(blockNameIdent, 829 TokenTypes.RCURLY); 830 for (DetailAST currentRcurly : rcurlyTokens) { 831 final DetailAST parent = currentRcurly.getParent(); 832 if (TokenUtil.areOnSameLine(blockStartToken, parent)) { 833 blockEndToken = currentRcurly; 834 } 835 } 836 } 837 return blockEndToken; 838 } 839 840 /** 841 * Checks whether the current variable is returned from the method. 842 * 843 * @param currentFrame current frame. 844 * @param ident variable ident token. 845 * @return true if the current variable is returned from the method. 846 */ 847 private static boolean isReturnedVariable(AbstractFrame currentFrame, DetailAST ident) { 848 final DetailAST blockFrameNameIdent = currentFrame.getFrameNameIdent(); 849 final DetailAST definitionToken = blockFrameNameIdent.getParent(); 850 final DetailAST blockStartToken = definitionToken.findFirstToken(TokenTypes.SLIST); 851 final DetailAST blockEndToken = getBlockEndToken(blockFrameNameIdent, blockStartToken); 852 853 final Set<DetailAST> returnsInsideBlock = getAllTokensOfType(definitionToken, 854 TokenTypes.LITERAL_RETURN, blockEndToken.getLineNo()); 855 856 return returnsInsideBlock.stream() 857 .anyMatch(returnToken -> isAstInside(returnToken, ident)); 858 } 859 860 /** 861 * Checks if the given {@code ast} is equal to the {@code tree} or a child of it. 862 * 863 * @param tree The tree to search. 864 * @param ast The AST to look for. 865 * @return {@code true} if the {@code ast} was found. 866 */ 867 private static boolean isAstInside(DetailAST tree, DetailAST ast) { 868 boolean result = false; 869 870 if (isAstSimilar(tree, ast)) { 871 result = true; 872 } 873 else { 874 for (DetailAST child = tree.getFirstChild(); child != null 875 && !result; child = child.getNextSibling()) { 876 result = isAstInside(child, ast); 877 } 878 } 879 880 return result; 881 } 882 883 /** 884 * Checks whether a field can be referenced from a static context. 885 * 886 * @param ident ident token. 887 * @return true if field can be referenced from a static context. 888 */ 889 private static boolean canBeReferencedFromStaticContext(DetailAST ident) { 890 boolean staticContext = false; 891 892 final DetailAST codeBlockDefinition = getCodeBlockDefinitionToken(ident); 893 if (codeBlockDefinition != null) { 894 final DetailAST modifiers = codeBlockDefinition.getFirstChild(); 895 staticContext = codeBlockDefinition.getType() == TokenTypes.STATIC_INIT 896 || modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 897 } 898 return !staticContext; 899 } 900 901 /** 902 * Returns code block definition token for current identifier. 903 * 904 * @param ident ident token. 905 * @return code block definition token for current identifier or null if code block 906 * definition was not found. 907 */ 908 private static DetailAST getCodeBlockDefinitionToken(DetailAST ident) { 909 DetailAST parent = ident; 910 while (parent != null 911 && parent.getType() != TokenTypes.METHOD_DEF 912 && parent.getType() != TokenTypes.STATIC_INIT) { 913 parent = parent.getParent(); 914 } 915 return parent; 916 } 917 918 /** 919 * Checks whether a value can be assigned to a field. 920 * A value can be assigned to a final field only in constructor block. If there is a method 921 * block, value assignment can be performed only to non final field. 922 * 923 * @param ast an identifier token. 924 * @return true if a value can be assigned to a field. 925 */ 926 private boolean canAssignValueToClassField(DetailAST ast) { 927 final AbstractFrame fieldUsageFrame = findFrame(ast, false); 928 final boolean fieldUsageInConstructor = isInsideConstructorFrame(fieldUsageFrame); 929 930 final AbstractFrame declarationFrame = findFrame(ast, true); 931 final boolean finalField = ((ClassFrame) declarationFrame).hasFinalField(ast); 932 933 return fieldUsageInConstructor || !finalField; 934 } 935 936 /** 937 * Checks whether a field usage frame is inside constructor frame. 938 * 939 * @param frame frame, where field is used. 940 * @return true if the field usage frame is inside constructor frame. 941 */ 942 private static boolean isInsideConstructorFrame(AbstractFrame frame) { 943 AbstractFrame fieldUsageFrame = frame; 944 while (fieldUsageFrame.getType() == FrameType.BLOCK_FRAME) { 945 fieldUsageFrame = fieldUsageFrame.getParent(); 946 } 947 return fieldUsageFrame.getType() == FrameType.CTOR_FRAME; 948 } 949 950 /** 951 * Checks whether an overlapping by method or constructor argument takes place. 952 * 953 * @param ast an identifier. 954 * @return true if an overlapping by method or constructor argument takes place. 955 */ 956 private boolean isOverlappingByArgument(DetailAST ast) { 957 boolean overlapping = false; 958 final DetailAST parent = ast.getParent(); 959 final DetailAST sibling = ast.getNextSibling(); 960 if (sibling != null && isAssignToken(parent.getType())) { 961 if (isCompoundAssignToken(parent.getType())) { 962 overlapping = true; 963 } 964 else { 965 final ClassFrame classFrame = (ClassFrame) findFrame(ast, true); 966 final Set<DetailAST> exprIdents = getAllTokensOfType(sibling, TokenTypes.IDENT); 967 overlapping = classFrame.containsFieldOrVariableDef(exprIdents, ast); 968 } 969 } 970 return overlapping; 971 } 972 973 /** 974 * Checks whether an overlapping by local variable takes place. 975 * 976 * @param ast an identifier. 977 * @return true if an overlapping by local variable takes place. 978 */ 979 private boolean isOverlappingByLocalVariable(DetailAST ast) { 980 boolean overlapping = false; 981 final DetailAST parent = ast.getParent(); 982 if (isAssignToken(parent.getType())) { 983 final ClassFrame classFrame = (ClassFrame) findFrame(ast, true); 984 final Set<DetailAST> exprIdents = 985 getAllTokensOfType(ast.getNextSibling(), TokenTypes.IDENT); 986 overlapping = classFrame.containsFieldOrVariableDef(exprIdents, ast); 987 } 988 return overlapping; 989 } 990 991 /** 992 * Collects all tokens of specific type starting with the current ast node. 993 * 994 * @param ast ast node. 995 * @param tokenType token type. 996 * @return a set of all tokens of specific type starting with the current ast node. 997 */ 998 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) { 999 DetailAST vertex = ast; 1000 final Set<DetailAST> result = new HashSet<>(); 1001 final Deque<DetailAST> stack = new ArrayDeque<>(); 1002 while (vertex != null || !stack.isEmpty()) { 1003 if (!stack.isEmpty()) { 1004 vertex = stack.pop(); 1005 } 1006 while (vertex != null) { 1007 if (vertex.getType() == tokenType) { 1008 result.add(vertex); 1009 } 1010 if (vertex.getNextSibling() != null) { 1011 stack.push(vertex.getNextSibling()); 1012 } 1013 vertex = vertex.getFirstChild(); 1014 } 1015 } 1016 return result; 1017 } 1018 1019 /** 1020 * Collects all tokens of specific type starting with the current ast node and which line 1021 * number is lower or equal to the end line number. 1022 * 1023 * @param ast ast node. 1024 * @param tokenType token type. 1025 * @param endLineNumber end line number. 1026 * @return a set of all tokens of specific type starting with the current ast node and which 1027 * line number is lower or equal to the end line number. 1028 */ 1029 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType, 1030 int endLineNumber) { 1031 DetailAST vertex = ast; 1032 final Set<DetailAST> result = new HashSet<>(); 1033 final Deque<DetailAST> stack = new ArrayDeque<>(); 1034 while (vertex != null || !stack.isEmpty()) { 1035 if (!stack.isEmpty()) { 1036 vertex = stack.pop(); 1037 } 1038 while (vertex != null) { 1039 if (tokenType == vertex.getType() 1040 && vertex.getLineNo() <= endLineNumber) { 1041 result.add(vertex); 1042 } 1043 if (vertex.getNextSibling() != null) { 1044 stack.push(vertex.getNextSibling()); 1045 } 1046 vertex = vertex.getFirstChild(); 1047 } 1048 } 1049 return result; 1050 } 1051 1052 /** 1053 * Collects all tokens which are equal to current token starting with the current ast node and 1054 * which line number is lower or equal to the end line number. 1055 * 1056 * @param ast ast node. 1057 * @param token token. 1058 * @param endLineNumber end line number. 1059 * @return a set of tokens which are equal to current token starting with the current ast node 1060 * and which line number is lower or equal to the end line number. 1061 */ 1062 private static Set<DetailAST> getAllTokensWhichAreEqualToCurrent(DetailAST ast, DetailAST token, 1063 int endLineNumber) { 1064 DetailAST vertex = ast; 1065 final Set<DetailAST> result = new HashSet<>(); 1066 final Deque<DetailAST> stack = new ArrayDeque<>(); 1067 while (vertex != null || !stack.isEmpty()) { 1068 if (!stack.isEmpty()) { 1069 vertex = stack.pop(); 1070 } 1071 while (vertex != null) { 1072 if (isAstSimilar(token, vertex) 1073 && vertex.getLineNo() <= endLineNumber) { 1074 result.add(vertex); 1075 } 1076 if (vertex.getNextSibling() != null) { 1077 stack.push(vertex.getNextSibling()); 1078 } 1079 vertex = vertex.getFirstChild(); 1080 } 1081 } 1082 return result; 1083 } 1084 1085 /** 1086 * Returns the frame where the method is declared, if the given method is used without 1087 * 'this' and null otherwise. 1088 * 1089 * @param ast the IDENT ast of the name to check. 1090 * @return the frame where the method is declared, if the given method is used without 1091 * 'this' and null otherwise. 1092 */ 1093 private AbstractFrame getMethodWithoutThis(DetailAST ast) { 1094 AbstractFrame result = null; 1095 if (!validateOnlyOverlapping) { 1096 final AbstractFrame frame = findFrame(ast, true); 1097 if (frame != null 1098 && ((ClassFrame) frame).hasInstanceMethod(ast) 1099 && !((ClassFrame) frame).hasStaticMethod(ast)) { 1100 result = frame; 1101 } 1102 } 1103 return result; 1104 } 1105 1106 /** 1107 * Find the class frame containing declaration. 1108 * 1109 * @param name IDENT ast of the declaration to find. 1110 * @param lookForMethod whether we are looking for a method name. 1111 * @return AbstractFrame containing declaration or null. 1112 */ 1113 private AbstractFrame findClassFrame(DetailAST name, boolean lookForMethod) { 1114 AbstractFrame frame = current.peek(); 1115 1116 while (true) { 1117 frame = findFrame(frame, name, lookForMethod); 1118 1119 if (frame == null || frame instanceof ClassFrame) { 1120 break; 1121 } 1122 1123 frame = frame.getParent(); 1124 } 1125 1126 return frame; 1127 } 1128 1129 /** 1130 * Find frame containing declaration. 1131 * 1132 * @param name IDENT ast of the declaration to find. 1133 * @param lookForMethod whether we are looking for a method name. 1134 * @return AbstractFrame containing declaration or null. 1135 */ 1136 private AbstractFrame findFrame(DetailAST name, boolean lookForMethod) { 1137 return findFrame(current.peek(), name, lookForMethod); 1138 } 1139 1140 /** 1141 * Find frame containing declaration. 1142 * 1143 * @param frame The parent frame to searching in. 1144 * @param name IDENT ast of the declaration to find. 1145 * @param lookForMethod whether we are looking for a method name. 1146 * @return AbstractFrame containing declaration or null. 1147 */ 1148 private static AbstractFrame findFrame(AbstractFrame frame, DetailAST name, 1149 boolean lookForMethod) { 1150 return frame.getIfContains(name, lookForMethod); 1151 } 1152 1153 /** 1154 * Check that token is related to Definition tokens. 1155 * 1156 * @param parentType token Type. 1157 * @return true if token is related to Definition Tokens. 1158 */ 1159 private static boolean isDeclarationToken(int parentType) { 1160 return DECLARATION_TOKENS.get(parentType); 1161 } 1162 1163 /** 1164 * Check that token is related to assign tokens. 1165 * 1166 * @param tokenType token type. 1167 * @return true if token is related to assign tokens. 1168 */ 1169 private static boolean isAssignToken(int tokenType) { 1170 return ASSIGN_TOKENS.get(tokenType); 1171 } 1172 1173 /** 1174 * Check that token is related to compound assign tokens. 1175 * 1176 * @param tokenType token type. 1177 * @return true if token is related to compound assign tokens. 1178 */ 1179 private static boolean isCompoundAssignToken(int tokenType) { 1180 return COMPOUND_ASSIGN_TOKENS.get(tokenType); 1181 } 1182 1183 /** 1184 * Gets the name of the nearest parent ClassFrame. 1185 * 1186 * @return the name of the nearest parent ClassFrame. 1187 */ 1188 private String getNearestClassFrameName() { 1189 AbstractFrame frame = current.peek(); 1190 while (frame.getType() != FrameType.CLASS_FRAME) { 1191 frame = frame.getParent(); 1192 } 1193 return frame.getFrameName(); 1194 } 1195 1196 /** 1197 * Checks if the token is a Lambda parameter. 1198 * 1199 * @param ast the {@code DetailAST} value of the token to be checked 1200 * @return true if the token is a Lambda parameter 1201 */ 1202 private static boolean isLambdaParameter(DetailAST ast) { 1203 DetailAST parent; 1204 for (parent = ast; parent != null; parent = parent.getParent()) { 1205 if (parent.getType() == TokenTypes.LAMBDA) { 1206 break; 1207 } 1208 } 1209 final boolean isLambdaParameter; 1210 if (parent == null) { 1211 isLambdaParameter = false; 1212 } 1213 else if (ast.getType() == TokenTypes.PARAMETER_DEF) { 1214 isLambdaParameter = true; 1215 } 1216 else { 1217 final DetailAST lambdaParameters = parent.findFirstToken(TokenTypes.PARAMETERS); 1218 if (lambdaParameters == null) { 1219 isLambdaParameter = parent.getFirstChild().getText().equals(ast.getText()); 1220 } 1221 else { 1222 isLambdaParameter = TokenUtil.findFirstTokenByPredicate(lambdaParameters, 1223 paramDef -> { 1224 final DetailAST param = paramDef.findFirstToken(TokenTypes.IDENT); 1225 return param != null && param.getText().equals(ast.getText()); 1226 }).isPresent(); 1227 } 1228 } 1229 return isLambdaParameter; 1230 } 1231 1232 /** 1233 * Checks if 2 AST are similar by their type and text. 1234 * 1235 * @param left The first AST to check. 1236 * @param right The second AST to check. 1237 * @return {@code true} if they are similar. 1238 */ 1239 private static boolean isAstSimilar(DetailAST left, DetailAST right) { 1240 return left.getType() == right.getType() && left.getText().equals(right.getText()); 1241 } 1242 1243 /** An AbstractFrame type. */ 1244 private enum FrameType { 1245 1246 /** Class frame type. */ 1247 CLASS_FRAME, 1248 /** Constructor frame type. */ 1249 CTOR_FRAME, 1250 /** Method frame type. */ 1251 METHOD_FRAME, 1252 /** Block frame type. */ 1253 BLOCK_FRAME, 1254 /** Catch frame type. */ 1255 CATCH_FRAME, 1256 /** For frame type. */ 1257 FOR_FRAME, 1258 /** Try with resources frame type. */ 1259 TRY_WITH_RESOURCES_FRAME 1260 1261 } 1262 1263 /** 1264 * A declaration frame. 1265 */ 1266 private abstract static class AbstractFrame { 1267 1268 /** Set of name of variables declared in this frame. */ 1269 private final Set<DetailAST> varIdents; 1270 1271 /** Parent frame. */ 1272 private final AbstractFrame parent; 1273 1274 /** Name identifier token. */ 1275 private final DetailAST frameNameIdent; 1276 1277 /** 1278 * Constructor -- invocable only via super() from subclasses. 1279 * 1280 * @param parent parent frame. 1281 * @param ident frame name ident. 1282 */ 1283 protected AbstractFrame(AbstractFrame parent, DetailAST ident) { 1284 this.parent = parent; 1285 frameNameIdent = ident; 1286 varIdents = new HashSet<>(); 1287 } 1288 1289 /** 1290 * Get the type of the frame. 1291 * 1292 * @return a FrameType. 1293 */ 1294 protected abstract FrameType getType(); 1295 1296 /** 1297 * Add a name to the frame. 1298 * 1299 * @param identToAdd the name we're adding. 1300 */ 1301 private void addIdent(DetailAST identToAdd) { 1302 varIdents.add(identToAdd); 1303 } 1304 1305 /** 1306 * Returns the parent frame. 1307 * 1308 * @return the parent frame 1309 */ 1310 protected AbstractFrame getParent() { 1311 return parent; 1312 } 1313 1314 /** 1315 * Returns the name identifier text. 1316 * 1317 * @return the name identifier text 1318 */ 1319 protected String getFrameName() { 1320 return frameNameIdent.getText(); 1321 } 1322 1323 /** 1324 * Returns the name identifier token. 1325 * 1326 * @return the name identifier token 1327 */ 1328 public DetailAST getFrameNameIdent() { 1329 return frameNameIdent; 1330 } 1331 1332 /** 1333 * Check whether the frame contains a field or a variable with the given name. 1334 * 1335 * @param identToFind the IDENT ast of the name we're looking for. 1336 * @return whether it was found. 1337 */ 1338 protected boolean containsFieldOrVariable(DetailAST identToFind) { 1339 return containsFieldOrVariableDef(varIdents, identToFind); 1340 } 1341 1342 /** 1343 * Check whether the frame contains a given name. 1344 * 1345 * @param identToFind IDENT ast of the name we're looking for. 1346 * @param lookForMethod whether we are looking for a method name. 1347 * @return whether it was found. 1348 */ 1349 protected AbstractFrame getIfContains(DetailAST identToFind, boolean lookForMethod) { 1350 final AbstractFrame frame; 1351 1352 if (!lookForMethod 1353 && containsFieldOrVariable(identToFind)) { 1354 frame = this; 1355 } 1356 else { 1357 frame = parent.getIfContains(identToFind, lookForMethod); 1358 } 1359 return frame; 1360 } 1361 1362 /** 1363 * Whether the set contains a declaration with the text of the specified 1364 * IDENT ast and it is declared in a proper position. 1365 * 1366 * @param set the set of declarations. 1367 * @param ident the specified IDENT ast. 1368 * @return true if the set contains a declaration with the text of the specified 1369 * IDENT ast and it is declared in a proper position. 1370 */ 1371 protected boolean containsFieldOrVariableDef(Set<DetailAST> set, DetailAST ident) { 1372 boolean result = false; 1373 for (DetailAST ast: set) { 1374 if (isProperDefinition(ident, ast)) { 1375 result = true; 1376 break; 1377 } 1378 } 1379 return result; 1380 } 1381 1382 /** 1383 * Whether the definition is correspondent to the IDENT. 1384 * 1385 * @param ident the IDENT ast to check. 1386 * @param ast the IDENT ast of the definition to check. 1387 * @return true if ast is correspondent to ident. 1388 */ 1389 protected boolean isProperDefinition(DetailAST ident, DetailAST ast) { 1390 final String identToFind = ident.getText(); 1391 return identToFind.equals(ast.getText()) 1392 && CheckUtil.isBeforeInSource(ast, ident); 1393 } 1394 } 1395 1396 /** 1397 * A frame initiated at method definition; holds a method definition token. 1398 */ 1399 private static class MethodFrame extends AbstractFrame { 1400 1401 /** 1402 * Creates method frame. 1403 * 1404 * @param parent parent frame. 1405 * @param ident method name identifier token. 1406 */ 1407 protected MethodFrame(AbstractFrame parent, DetailAST ident) { 1408 super(parent, ident); 1409 } 1410 1411 @Override 1412 protected FrameType getType() { 1413 return FrameType.METHOD_FRAME; 1414 } 1415 1416 } 1417 1418 /** 1419 * A frame initiated at constructor definition. 1420 */ 1421 private static class ConstructorFrame extends AbstractFrame { 1422 1423 /** 1424 * Creates a constructor frame. 1425 * 1426 * @param parent parent frame. 1427 * @param ident frame name ident. 1428 */ 1429 protected ConstructorFrame(AbstractFrame parent, DetailAST ident) { 1430 super(parent, ident); 1431 } 1432 1433 @Override 1434 protected FrameType getType() { 1435 return FrameType.CTOR_FRAME; 1436 } 1437 1438 } 1439 1440 /** 1441 * A frame initiated at class, enum or interface definition; holds instance variable names. 1442 */ 1443 private static class ClassFrame extends AbstractFrame { 1444 1445 /** Set of idents of instance members declared in this frame. */ 1446 private final Set<DetailAST> instanceMembers; 1447 /** Set of idents of instance methods declared in this frame. */ 1448 private final Set<DetailAST> instanceMethods; 1449 /** Set of idents of variables declared in this frame. */ 1450 private final Set<DetailAST> staticMembers; 1451 /** Set of idents of static methods declared in this frame. */ 1452 private final Set<DetailAST> staticMethods; 1453 1454 /** 1455 * Creates new instance of ClassFrame. 1456 * 1457 * @param parent parent frame. 1458 * @param ident frame name ident. 1459 */ 1460 private ClassFrame(AbstractFrame parent, DetailAST ident) { 1461 super(parent, ident); 1462 instanceMembers = new HashSet<>(); 1463 instanceMethods = new HashSet<>(); 1464 staticMembers = new HashSet<>(); 1465 staticMethods = new HashSet<>(); 1466 } 1467 1468 @Override 1469 protected FrameType getType() { 1470 return FrameType.CLASS_FRAME; 1471 } 1472 1473 /** 1474 * Adds static member's ident. 1475 * 1476 * @param ident an ident of static member of the class. 1477 */ 1478 public void addStaticMember(final DetailAST ident) { 1479 staticMembers.add(ident); 1480 } 1481 1482 /** 1483 * Adds static method's name. 1484 * 1485 * @param ident an ident of static method of the class. 1486 */ 1487 public void addStaticMethod(final DetailAST ident) { 1488 staticMethods.add(ident); 1489 } 1490 1491 /** 1492 * Adds instance member's ident. 1493 * 1494 * @param ident an ident of instance member of the class. 1495 */ 1496 public void addInstanceMember(final DetailAST ident) { 1497 instanceMembers.add(ident); 1498 } 1499 1500 /** 1501 * Adds instance method's name. 1502 * 1503 * @param ident an ident of instance method of the class. 1504 */ 1505 public void addInstanceMethod(final DetailAST ident) { 1506 instanceMethods.add(ident); 1507 } 1508 1509 /** 1510 * Checks if a given name is a known instance member of the class. 1511 * 1512 * @param ident the IDENT ast of the name to check. 1513 * @return true is the given name is a name of a known 1514 * instance member of the class. 1515 */ 1516 public boolean hasInstanceMember(final DetailAST ident) { 1517 return containsFieldOrVariableDef(instanceMembers, ident); 1518 } 1519 1520 /** 1521 * Checks if a given name is a known instance method of the class. 1522 * 1523 * @param ident the IDENT ast of the method call to check. 1524 * @return true if the given ast is correspondent to a known 1525 * instance method of the class. 1526 */ 1527 public boolean hasInstanceMethod(final DetailAST ident) { 1528 return containsMethodDef(instanceMethods, ident); 1529 } 1530 1531 /** 1532 * Checks if a given name is a known static method of the class. 1533 * 1534 * @param ident the IDENT ast of the method call to check. 1535 * @return true is the given ast is correspondent to a known 1536 * instance method of the class. 1537 */ 1538 public boolean hasStaticMethod(final DetailAST ident) { 1539 return containsMethodDef(staticMethods, ident); 1540 } 1541 1542 /** 1543 * Checks whether given instance member has final modifier. 1544 * 1545 * @param instanceMember an instance member of a class. 1546 * @return true if given instance member has final modifier. 1547 */ 1548 public boolean hasFinalField(final DetailAST instanceMember) { 1549 boolean result = false; 1550 for (DetailAST member : instanceMembers) { 1551 final DetailAST parent = member.getParent(); 1552 if (parent.getType() == TokenTypes.RECORD_COMPONENT_DEF) { 1553 result = true; 1554 } 1555 else { 1556 final DetailAST mods = parent.findFirstToken(TokenTypes.MODIFIERS); 1557 final boolean finalMod = mods.findFirstToken(TokenTypes.FINAL) != null; 1558 if (finalMod && isAstSimilar(member, instanceMember)) { 1559 result = true; 1560 } 1561 } 1562 } 1563 return result; 1564 } 1565 1566 @Override 1567 protected boolean containsFieldOrVariable(DetailAST identToFind) { 1568 return containsFieldOrVariableDef(instanceMembers, identToFind) 1569 || containsFieldOrVariableDef(staticMembers, identToFind); 1570 } 1571 1572 @Override 1573 protected boolean isProperDefinition(DetailAST ident, DetailAST ast) { 1574 final String identToFind = ident.getText(); 1575 return identToFind.equals(ast.getText()); 1576 } 1577 1578 @Override 1579 protected AbstractFrame getIfContains(DetailAST identToFind, boolean lookForMethod) { 1580 AbstractFrame frame = null; 1581 1582 if (containsMethod(identToFind) 1583 || containsFieldOrVariable(identToFind)) { 1584 frame = this; 1585 } 1586 else if (getParent() != null) { 1587 frame = getParent().getIfContains(identToFind, lookForMethod); 1588 } 1589 return frame; 1590 } 1591 1592 /** 1593 * Check whether the frame contains a given method. 1594 * 1595 * @param methodToFind the AST of the method to find. 1596 * @return true, if a method with the same name and number of parameters is found. 1597 */ 1598 private boolean containsMethod(DetailAST methodToFind) { 1599 return containsMethodDef(instanceMethods, methodToFind) 1600 || containsMethodDef(staticMethods, methodToFind); 1601 } 1602 1603 /** 1604 * Whether the set contains a method definition with the 1605 * same name and number of parameters. 1606 * 1607 * @param set the set of definitions. 1608 * @param ident the specified method call IDENT ast. 1609 * @return true if the set contains a definition with the 1610 * same name and number of parameters. 1611 */ 1612 private static boolean containsMethodDef(Set<DetailAST> set, DetailAST ident) { 1613 boolean result = false; 1614 for (DetailAST ast: set) { 1615 if (isSimilarSignature(ident, ast)) { 1616 result = true; 1617 break; 1618 } 1619 } 1620 return result; 1621 } 1622 1623 /** 1624 * Whether the method definition has the same name and number of parameters. 1625 * 1626 * @param ident the specified method call IDENT ast. 1627 * @param ast the ast of a method definition to compare with. 1628 * @return true if a method definition has the same name and number of parameters 1629 * as the method call. 1630 */ 1631 private static boolean isSimilarSignature(DetailAST ident, DetailAST ast) { 1632 boolean result = false; 1633 final DetailAST elistToken = ident.getParent().findFirstToken(TokenTypes.ELIST); 1634 if (elistToken != null && ident.getText().equals(ast.getText())) { 1635 final int paramsNumber = 1636 ast.getParent().findFirstToken(TokenTypes.PARAMETERS).getChildCount(); 1637 final int argsNumber = elistToken.getChildCount(); 1638 result = paramsNumber == argsNumber; 1639 } 1640 return result; 1641 } 1642 1643 } 1644 1645 /** 1646 * An anonymous class frame; holds instance variable names. 1647 */ 1648 private static class AnonymousClassFrame extends ClassFrame { 1649 1650 /** The name of the frame. */ 1651 private final String frameName; 1652 1653 /** 1654 * Creates anonymous class frame. 1655 * 1656 * @param parent parent frame. 1657 * @param frameName name of the frame. 1658 */ 1659 protected AnonymousClassFrame(AbstractFrame parent, String frameName) { 1660 super(parent, null); 1661 this.frameName = frameName; 1662 } 1663 1664 @Override 1665 protected String getFrameName() { 1666 return frameName; 1667 } 1668 1669 } 1670 1671 /** 1672 * A frame initiated on entering a statement list; holds local variable names. 1673 */ 1674 private static class BlockFrame extends AbstractFrame { 1675 1676 /** 1677 * Creates block frame. 1678 * 1679 * @param parent parent frame. 1680 * @param ident ident frame name ident. 1681 */ 1682 protected BlockFrame(AbstractFrame parent, DetailAST ident) { 1683 super(parent, ident); 1684 } 1685 1686 @Override 1687 protected FrameType getType() { 1688 return FrameType.BLOCK_FRAME; 1689 } 1690 1691 } 1692 1693 /** 1694 * A frame initiated on entering a catch block; holds local catch variable names. 1695 */ 1696 private static class CatchFrame extends AbstractFrame { 1697 1698 /** 1699 * Creates catch frame. 1700 * 1701 * @param parent parent frame. 1702 * @param ident ident frame name ident. 1703 */ 1704 protected CatchFrame(AbstractFrame parent, DetailAST ident) { 1705 super(parent, ident); 1706 } 1707 1708 @Override 1709 public FrameType getType() { 1710 return FrameType.CATCH_FRAME; 1711 } 1712 1713 @Override 1714 protected AbstractFrame getIfContains(DetailAST identToFind, boolean lookForMethod) { 1715 final AbstractFrame frame; 1716 1717 if (!lookForMethod 1718 && containsFieldOrVariable(identToFind)) { 1719 frame = this; 1720 } 1721 else if (getParent().getType() == FrameType.TRY_WITH_RESOURCES_FRAME) { 1722 // Skip try-with-resources frame because resources cannot be accessed from catch 1723 frame = getParent().getParent().getIfContains(identToFind, lookForMethod); 1724 } 1725 else { 1726 frame = getParent().getIfContains(identToFind, lookForMethod); 1727 } 1728 return frame; 1729 } 1730 1731 } 1732 1733 /** 1734 * A frame initiated on entering a for block; holds local for variable names. 1735 */ 1736 private static class ForFrame extends AbstractFrame { 1737 1738 /** 1739 * Creates for frame. 1740 * 1741 * @param parent parent frame. 1742 * @param ident ident frame name ident. 1743 */ 1744 protected ForFrame(AbstractFrame parent, DetailAST ident) { 1745 super(parent, ident); 1746 } 1747 1748 @Override 1749 public FrameType getType() { 1750 return FrameType.FOR_FRAME; 1751 } 1752 1753 } 1754 1755 /** 1756 * A frame initiated on entering a try-with-resources construct; 1757 * holds local resources for the try block. 1758 */ 1759 private static class TryWithResourcesFrame extends AbstractFrame { 1760 1761 /** 1762 * Creates try-with-resources frame. 1763 * 1764 * @param parent parent frame. 1765 * @param ident ident frame name ident. 1766 */ 1767 protected TryWithResourcesFrame(AbstractFrame parent, DetailAST ident) { 1768 super(parent, ident); 1769 } 1770 1771 @Override 1772 public FrameType getType() { 1773 return FrameType.TRY_WITH_RESOURCES_FRAME; 1774 } 1775 1776 } 1777 1778}