001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.Arrays; 024import java.util.Deque; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Optional; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 036 037/** 038 * <p> 039 * Checks that local variables that never have their values changed are declared final. 040 * The check can be configured to also check that unchanged parameters are declared final. 041 * </p> 042 * <p> 043 * When configured to check parameters, the check ignores parameters of interface 044 * methods and abstract methods. 045 * </p> 046 * <ul> 047 * <li> 048 * Property {@code validateEnhancedForLoopVariable} - Control whether to check 049 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 050 * enhanced for-loop</a> variable. 051 * Default value is {@code false}. 052 * </li> 053 * <li> 054 * Property {@code tokens} - tokens to check 055 * Default value is: 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 057 * VARIABLE_DEF</a>. 058 * </li> 059 * </ul> 060 * <p> 061 * To configure the check: 062 * </p> 063 * <pre> 064 * <module name="FinalLocalVariable"/> 065 * </pre> 066 * <p> 067 * To configure the check so that it checks local variables and parameters: 068 * </p> 069 * <pre> 070 * <module name="FinalLocalVariable"> 071 * <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/> 072 * </module> 073 * </pre> 074 * <p> 075 * By default, this Check skip final validation on 076 * <a href = "https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 077 * Enhanced For-Loop</a>. 078 * </p> 079 * <p> 080 * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable 081 * from Enhanced For Loop. 082 * </p> 083 * <p> 084 * An example of how to configure the check so that it also validates enhanced For Loop Variable is: 085 * </p> 086 * <pre> 087 * <module name="FinalLocalVariable"> 088 * <property name="tokens" value="VARIABLE_DEF"/> 089 * <property name="validateEnhancedForLoopVariable" value="true"/> 090 * </module> 091 * </pre> 092 * <p>Example:</p> 093 * <pre> 094 * for (int number : myNumbers) { // violation 095 * System.out.println(number); 096 * } 097 * </pre> 098 * <p> 099 * An example of how to configure check on local variables and parameters 100 * but do not validate loop variables: 101 * </p> 102 * <pre> 103 * <module name="FinalLocalVariable"> 104 * <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/> 105 * <property name="validateEnhancedForLoopVariable" value="false"/> 106 * </module> 107 * </pre> 108 * <p> 109 * Example: 110 * </p> 111 * <pre> 112 * public class MyClass { 113 * static int foo(int x, int y) { //violations, parameters should be final 114 * return x+y; 115 * } 116 * public static void main (String []args) { //violation, parameters should be final 117 * for (String i : args) { 118 * System.out.println(i); 119 * } 120 * int result=foo(1,2); // violation 121 * } 122 * } 123 * </pre> 124 * 125 * @since 3.2 126 */ 127@FileStatefulCheck 128public class FinalLocalVariableCheck extends AbstractCheck { 129 130 /** 131 * A key is pointing to the warning message text in "messages.properties" 132 * file. 133 */ 134 public static final String MSG_KEY = "final.variable"; 135 136 /** 137 * Assign operator types. 138 */ 139 private static final int[] ASSIGN_OPERATOR_TYPES = { 140 TokenTypes.POST_INC, 141 TokenTypes.POST_DEC, 142 TokenTypes.ASSIGN, 143 TokenTypes.PLUS_ASSIGN, 144 TokenTypes.MINUS_ASSIGN, 145 TokenTypes.STAR_ASSIGN, 146 TokenTypes.DIV_ASSIGN, 147 TokenTypes.MOD_ASSIGN, 148 TokenTypes.SR_ASSIGN, 149 TokenTypes.BSR_ASSIGN, 150 TokenTypes.SL_ASSIGN, 151 TokenTypes.BAND_ASSIGN, 152 TokenTypes.BXOR_ASSIGN, 153 TokenTypes.BOR_ASSIGN, 154 TokenTypes.INC, 155 TokenTypes.DEC, 156 }; 157 158 /** 159 * Loop types. 160 */ 161 private static final int[] LOOP_TYPES = { 162 TokenTypes.LITERAL_FOR, 163 TokenTypes.LITERAL_WHILE, 164 TokenTypes.LITERAL_DO, 165 }; 166 167 /** Scope Deque. */ 168 private final Deque<ScopeData> scopeStack = new ArrayDeque<>(); 169 170 /** Uninitialized variables of previous scope. */ 171 private final Deque<Deque<DetailAST>> prevScopeUninitializedVariables = 172 new ArrayDeque<>(); 173 174 /** Assigned variables of current scope. */ 175 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables = 176 new ArrayDeque<>(); 177 178 /** 179 * Control whether to check 180 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 181 * enhanced for-loop</a> variable. 182 */ 183 private boolean validateEnhancedForLoopVariable; 184 185 static { 186 // Array sorting for binary search 187 Arrays.sort(ASSIGN_OPERATOR_TYPES); 188 Arrays.sort(LOOP_TYPES); 189 } 190 191 /** 192 * Setter to control whether to check 193 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 194 * enhanced for-loop</a> variable. 195 * 196 * @param validateEnhancedForLoopVariable whether to check for-loop variable 197 */ 198 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) { 199 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable; 200 } 201 202 @Override 203 public int[] getRequiredTokens() { 204 return new int[] { 205 TokenTypes.IDENT, 206 TokenTypes.CTOR_DEF, 207 TokenTypes.METHOD_DEF, 208 TokenTypes.SLIST, 209 TokenTypes.OBJBLOCK, 210 TokenTypes.LITERAL_BREAK, 211 TokenTypes.LITERAL_FOR, 212 }; 213 } 214 215 @Override 216 public int[] getDefaultTokens() { 217 return new int[] { 218 TokenTypes.IDENT, 219 TokenTypes.CTOR_DEF, 220 TokenTypes.METHOD_DEF, 221 TokenTypes.SLIST, 222 TokenTypes.OBJBLOCK, 223 TokenTypes.LITERAL_BREAK, 224 TokenTypes.LITERAL_FOR, 225 TokenTypes.VARIABLE_DEF, 226 }; 227 } 228 229 @Override 230 public int[] getAcceptableTokens() { 231 return new int[] { 232 TokenTypes.IDENT, 233 TokenTypes.CTOR_DEF, 234 TokenTypes.METHOD_DEF, 235 TokenTypes.SLIST, 236 TokenTypes.OBJBLOCK, 237 TokenTypes.LITERAL_BREAK, 238 TokenTypes.LITERAL_FOR, 239 TokenTypes.VARIABLE_DEF, 240 TokenTypes.PARAMETER_DEF, 241 }; 242 } 243 244 // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block 245 // expressions to separate methods, but that will not increase readability. 246 @Override 247 public void visitToken(DetailAST ast) { 248 switch (ast.getType()) { 249 case TokenTypes.OBJBLOCK: 250 case TokenTypes.METHOD_DEF: 251 case TokenTypes.CTOR_DEF: 252 case TokenTypes.LITERAL_FOR: 253 scopeStack.push(new ScopeData()); 254 break; 255 case TokenTypes.SLIST: 256 currentScopeAssignedVariables.push(new ArrayDeque<>()); 257 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 258 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP) 259 == ast.getParent()) { 260 storePrevScopeUninitializedVariableData(); 261 scopeStack.push(new ScopeData()); 262 } 263 break; 264 case TokenTypes.PARAMETER_DEF: 265 if (!isInLambda(ast) 266 && ast.findFirstToken(TokenTypes.MODIFIERS) 267 .findFirstToken(TokenTypes.FINAL) == null 268 && !isInAbstractOrNativeMethod(ast) 269 && !ScopeUtil.isInInterfaceBlock(ast) 270 && !isMultipleTypeCatch(ast) 271 && !CheckUtil.isReceiverParameter(ast)) { 272 insertParameter(ast); 273 } 274 break; 275 case TokenTypes.VARIABLE_DEF: 276 if (ast.getParent().getType() != TokenTypes.OBJBLOCK 277 && ast.findFirstToken(TokenTypes.MODIFIERS) 278 .findFirstToken(TokenTypes.FINAL) == null 279 && !isVariableInForInit(ast) 280 && shouldCheckEnhancedForLoopVariable(ast)) { 281 insertVariable(ast); 282 } 283 break; 284 case TokenTypes.IDENT: 285 final int parentType = ast.getParent().getType(); 286 if (isAssignOperator(parentType) && isFirstChild(ast)) { 287 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast); 288 if (candidate.isPresent()) { 289 determineAssignmentConditions(ast, candidate.get()); 290 currentScopeAssignedVariables.peek().add(ast); 291 } 292 removeFinalVariableCandidateFromStack(ast); 293 } 294 break; 295 case TokenTypes.LITERAL_BREAK: 296 scopeStack.peek().containsBreak = true; 297 break; 298 default: 299 throw new IllegalStateException("Incorrect token type"); 300 } 301 } 302 303 @Override 304 public void leaveToken(DetailAST ast) { 305 Map<String, FinalVariableCandidate> scope = null; 306 switch (ast.getType()) { 307 case TokenTypes.OBJBLOCK: 308 case TokenTypes.CTOR_DEF: 309 case TokenTypes.METHOD_DEF: 310 case TokenTypes.LITERAL_FOR: 311 scope = scopeStack.pop().scope; 312 break; 313 case TokenTypes.SLIST: 314 // -@cs[MoveVariableInsideIf] assignment value is modified later so it can't be 315 // moved 316 final Deque<DetailAST> prevScopeUninitializedVariableData = 317 prevScopeUninitializedVariables.peek(); 318 boolean containsBreak = false; 319 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 320 || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(), 321 TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) { 322 containsBreak = scopeStack.peek().containsBreak; 323 scope = scopeStack.pop().scope; 324 prevScopeUninitializedVariables.pop(); 325 } 326 final DetailAST parent = ast.getParent(); 327 if (containsBreak || shouldUpdateUninitializedVariables(parent)) { 328 updateAllUninitializedVariables(prevScopeUninitializedVariableData); 329 } 330 updateCurrentScopeAssignedVariables(); 331 break; 332 default: 333 // do nothing 334 } 335 if (scope != null) { 336 for (FinalVariableCandidate candidate : scope.values()) { 337 final DetailAST ident = candidate.variableIdent; 338 log(ident, MSG_KEY, ident.getText()); 339 } 340 } 341 } 342 343 /** 344 * Update assigned variables in a temporary stack. 345 */ 346 private void updateCurrentScopeAssignedVariables() { 347 // -@cs[MoveVariableInsideIf] assignment value is a modification call so it can't be moved 348 final Deque<DetailAST> poppedScopeAssignedVariableData = 349 currentScopeAssignedVariables.pop(); 350 final Deque<DetailAST> currentScopeAssignedVariableData = 351 currentScopeAssignedVariables.peek(); 352 if (currentScopeAssignedVariableData != null) { 353 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); 354 } 355 } 356 357 /** 358 * Determines identifier assignment conditions (assigned or already assigned). 359 * 360 * @param ident identifier. 361 * @param candidate final local variable candidate. 362 */ 363 private static void determineAssignmentConditions(DetailAST ident, 364 FinalVariableCandidate candidate) { 365 if (candidate.assigned) { 366 if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE) 367 && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) { 368 candidate.alreadyAssigned = true; 369 } 370 } 371 else { 372 candidate.assigned = true; 373 } 374 } 375 376 /** 377 * Checks whether the scope of a node is restricted to a specific code block. 378 * 379 * @param node node. 380 * @param blockType block type. 381 * @return true if the scope of a node is restricted to a specific code block. 382 */ 383 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) { 384 boolean returnValue = false; 385 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { 386 final int type = token.getType(); 387 if (type == blockType) { 388 returnValue = true; 389 break; 390 } 391 } 392 return returnValue; 393 } 394 395 /** 396 * Gets final variable candidate for ast. 397 * 398 * @param ast ast. 399 * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. 400 */ 401 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) { 402 Optional<FinalVariableCandidate> result = Optional.empty(); 403 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 404 while (iterator.hasNext() && !result.isPresent()) { 405 final ScopeData scopeData = iterator.next(); 406 result = scopeData.findFinalVariableCandidateForAst(ast); 407 } 408 return result; 409 } 410 411 /** 412 * Store un-initialized variables in a temporary stack for future use. 413 */ 414 private void storePrevScopeUninitializedVariableData() { 415 final ScopeData scopeData = scopeStack.peek(); 416 final Deque<DetailAST> prevScopeUninitializedVariableData = 417 new ArrayDeque<>(); 418 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push); 419 prevScopeUninitializedVariables.push(prevScopeUninitializedVariableData); 420 } 421 422 /** 423 * Update current scope data uninitialized variable according to the whole scope data. 424 * 425 * @param prevScopeUninitializedVariableData variable for previous stack of uninitialized 426 * variables 427 * @noinspection MethodParameterNamingConvention 428 */ 429 private void updateAllUninitializedVariables( 430 Deque<DetailAST> prevScopeUninitializedVariableData) { 431 // Check for only previous scope 432 updateUninitializedVariables(prevScopeUninitializedVariableData); 433 // Check for rest of the scope 434 prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables); 435 } 436 437 /** 438 * Update current scope data uninitialized variable according to the specific scope data. 439 * 440 * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables 441 */ 442 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) { 443 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator(); 444 while (iterator.hasNext()) { 445 final DetailAST assignedVariable = iterator.next(); 446 boolean shouldRemove = false; 447 for (DetailAST variable : scopeUninitializedVariableData) { 448 for (ScopeData scopeData : scopeStack) { 449 final FinalVariableCandidate candidate = 450 scopeData.scope.get(variable.getText()); 451 DetailAST storedVariable = null; 452 if (candidate != null) { 453 storedVariable = candidate.variableIdent; 454 } 455 if (storedVariable != null 456 && isSameVariables(storedVariable, variable) 457 && isSameVariables(assignedVariable, variable)) { 458 scopeData.uninitializedVariables.push(variable); 459 shouldRemove = true; 460 } 461 } 462 } 463 if (shouldRemove) { 464 iterator.remove(); 465 } 466 } 467 } 468 469 /** 470 * If token is LITERAL_IF and there is an {@code else} following or token is CASE_GROUP and 471 * there is another {@code case} following, then update the uninitialized variables. 472 * 473 * @param ast token to be checked 474 * @return true if should be updated, else false 475 */ 476 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { 477 return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast); 478 } 479 480 /** 481 * If token is LITERAL_IF and there is an {@code else} following. 482 * 483 * @param ast token to be checked 484 * @return true if token is LITERAL_IF and there is an {@code else} following, else false 485 */ 486 private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) { 487 return ast.getType() == TokenTypes.LITERAL_IF 488 && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE; 489 } 490 491 /** 492 * If token is CASE_GROUP and there is another {@code case} following. 493 * 494 * @param ast token to be checked 495 * @return true if token is CASE_GROUP and there is another {@code case} following, else false 496 */ 497 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { 498 return ast.getType() == TokenTypes.CASE_GROUP 499 && findLastChildWhichContainsSpecifiedToken( 500 ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast; 501 } 502 503 /** 504 * Returns the last child token that makes a specified type and contains containType in 505 * its branch. 506 * 507 * @param ast token to be tested 508 * @param childType the token type to match 509 * @param containType the token type which has to be present in the branch 510 * @return the matching token, or null if no match 511 */ 512 private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType, 513 int containType) { 514 DetailAST returnValue = null; 515 for (DetailAST astIterator = ast.getFirstChild(); astIterator != null; 516 astIterator = astIterator.getNextSibling()) { 517 if (astIterator.getType() == childType 518 && astIterator.findFirstToken(containType) != null) { 519 returnValue = astIterator; 520 } 521 } 522 return returnValue; 523 } 524 525 /** 526 * Determines whether enhanced for-loop variable should be checked or not. 527 * 528 * @param ast The ast to compare. 529 * @return true if enhanced for-loop variable should be checked. 530 */ 531 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { 532 return validateEnhancedForLoopVariable 533 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; 534 } 535 536 /** 537 * Insert a parameter at the topmost scope stack. 538 * 539 * @param ast the variable to insert. 540 */ 541 private void insertParameter(DetailAST ast) { 542 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 543 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 544 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 545 } 546 547 /** 548 * Insert a variable at the topmost scope stack. 549 * 550 * @param ast the variable to insert. 551 */ 552 private void insertVariable(DetailAST ast) { 553 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 554 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 555 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); 556 // for-each variables are implicitly assigned 557 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE; 558 scope.put(astNode.getText(), candidate); 559 if (!isInitialized(astNode)) { 560 scopeStack.peek().uninitializedVariables.add(astNode); 561 } 562 } 563 564 /** 565 * Check if VARIABLE_DEF is initialized or not. 566 * 567 * @param ast VARIABLE_DEF to be checked 568 * @return true if initialized 569 */ 570 private static boolean isInitialized(DetailAST ast) { 571 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; 572 } 573 574 /** 575 * Whether the ast is the first child of its parent. 576 * 577 * @param ast the ast to check. 578 * @return true if the ast is the first child of its parent. 579 */ 580 private static boolean isFirstChild(DetailAST ast) { 581 return ast.getPreviousSibling() == null; 582 } 583 584 /** 585 * Removes the final variable candidate from the Stack. 586 * 587 * @param ast variable to remove. 588 */ 589 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 590 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 591 while (iterator.hasNext()) { 592 final ScopeData scopeData = iterator.next(); 593 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 594 final FinalVariableCandidate candidate = scope.get(ast.getText()); 595 DetailAST storedVariable = null; 596 if (candidate != null) { 597 storedVariable = candidate.variableIdent; 598 } 599 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 600 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 601 scope.remove(ast.getText()); 602 } 603 break; 604 } 605 } 606 } 607 608 /** 609 * Check if given parameter definition is a multiple type catch. 610 * 611 * @param parameterDefAst parameter definition 612 * @return true if it is a multiple type catch, false otherwise 613 */ 614 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 615 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 616 return typeAst.getFirstChild().getType() == TokenTypes.BOR; 617 } 618 619 /** 620 * Whether the final variable candidate should be removed from the list of final local variable 621 * candidates. 622 * 623 * @param scopeData the scope data of the variable. 624 * @param ast the variable ast. 625 * @return true, if the variable should be removed. 626 */ 627 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 628 boolean shouldRemove = true; 629 for (DetailAST variable : scopeData.uninitializedVariables) { 630 if (variable.getText().equals(ast.getText())) { 631 // if the variable is declared outside the loop and initialized inside 632 // the loop, then it cannot be declared final, as it can be initialized 633 // more than once in this case 634 if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) { 635 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 636 shouldRemove = candidate.alreadyAssigned; 637 } 638 scopeData.uninitializedVariables.remove(variable); 639 break; 640 } 641 } 642 return shouldRemove; 643 } 644 645 /** 646 * Checks whether a variable which is declared outside loop is used inside loop. 647 * For example: 648 * <p> 649 * {@code 650 * int x; 651 * for (int i = 0, j = 0; i < j; i++) { 652 * x = 5; 653 * } 654 * } 655 * </p> 656 * 657 * @param variable variable. 658 * @return true if a variable which is declared outside loop is used inside loop. 659 */ 660 private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) { 661 DetailAST loop2 = variable.getParent(); 662 while (loop2 != null 663 && !isLoopAst(loop2.getType())) { 664 loop2 = loop2.getParent(); 665 } 666 return loop2 != null; 667 } 668 669 /** 670 * Is Arithmetic operator. 671 * 672 * @param parentType token AST 673 * @return true is token type is in arithmetic operator 674 */ 675 private static boolean isAssignOperator(int parentType) { 676 return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0; 677 } 678 679 /** 680 * Checks if current variable is defined in 681 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 682 * <p> 683 * {@code 684 * for (int i = 0, j = 0; i < j; i++) { . . . } 685 * } 686 * </p> 687 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 688 * 689 * @param variableDef variable definition node. 690 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 691 */ 692 private static boolean isVariableInForInit(DetailAST variableDef) { 693 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 694 } 695 696 /** 697 * Determines whether an AST is a descendant of an abstract or native method. 698 * 699 * @param ast the AST to check. 700 * @return true if ast is a descendant of an abstract or native method. 701 */ 702 private static boolean isInAbstractOrNativeMethod(DetailAST ast) { 703 boolean abstractOrNative = false; 704 DetailAST parent = ast.getParent(); 705 while (parent != null && !abstractOrNative) { 706 if (parent.getType() == TokenTypes.METHOD_DEF) { 707 final DetailAST modifiers = 708 parent.findFirstToken(TokenTypes.MODIFIERS); 709 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null 710 || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null; 711 } 712 parent = parent.getParent(); 713 } 714 return abstractOrNative; 715 } 716 717 /** 718 * Check if current param is lambda's param. 719 * 720 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 721 * @return true if current param is lambda's param. 722 */ 723 private static boolean isInLambda(DetailAST paramDef) { 724 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 725 } 726 727 /** 728 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 729 * 730 * @param ast Variable for which we want to find the scope in which it is defined 731 * @return ast The Class or Constructor or Method in which it is defined. 732 */ 733 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 734 DetailAST astTraverse = ast; 735 while (astTraverse.getType() != TokenTypes.METHOD_DEF 736 && astTraverse.getType() != TokenTypes.CLASS_DEF 737 && astTraverse.getType() != TokenTypes.ENUM_DEF 738 && astTraverse.getType() != TokenTypes.CTOR_DEF 739 && !ScopeUtil.isClassFieldDef(astTraverse)) { 740 astTraverse = astTraverse.getParent(); 741 } 742 return astTraverse; 743 } 744 745 /** 746 * Check if both the Variables are same. 747 * 748 * @param ast1 Variable to compare 749 * @param ast2 Variable to compare 750 * @return true if both the variables are same, otherwise false 751 */ 752 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 753 final DetailAST classOrMethodOfAst1 = 754 findFirstUpperNamedBlock(ast1); 755 final DetailAST classOrMethodOfAst2 = 756 findFirstUpperNamedBlock(ast2); 757 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 758 } 759 760 /** 761 * Check if both the variables are in the same loop. 762 * 763 * @param ast1 variable to compare. 764 * @param ast2 variable to compare. 765 * @return true if both the variables are in the same loop. 766 */ 767 private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) { 768 DetailAST loop1 = ast1.getParent(); 769 while (loop1 != null && !isLoopAst(loop1.getType())) { 770 loop1 = loop1.getParent(); 771 } 772 DetailAST loop2 = ast2.getParent(); 773 while (loop2 != null && !isLoopAst(loop2.getType())) { 774 loop2 = loop2.getParent(); 775 } 776 return loop1 != null && loop1 == loop2; 777 } 778 779 /** 780 * Checks whether the ast is a loop. 781 * 782 * @param ast the ast to check. 783 * @return true if the ast is a loop. 784 */ 785 private static boolean isLoopAst(int ast) { 786 return Arrays.binarySearch(LOOP_TYPES, ast) >= 0; 787 } 788 789 /** 790 * Holder for the scope data. 791 */ 792 private static class ScopeData { 793 794 /** Contains variable definitions. */ 795 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 796 797 /** Contains definitions of uninitialized variables. */ 798 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 799 800 /** Whether there is a {@code break} in the scope. */ 801 private boolean containsBreak; 802 803 /** 804 * Searches for final local variable candidate for ast in the scope. 805 * 806 * @param ast ast. 807 * @return Optional of {@link FinalVariableCandidate}. 808 */ 809 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) { 810 Optional<FinalVariableCandidate> result = Optional.empty(); 811 DetailAST storedVariable = null; 812 final Optional<FinalVariableCandidate> candidate = 813 Optional.ofNullable(scope.get(ast.getText())); 814 if (candidate.isPresent()) { 815 storedVariable = candidate.get().variableIdent; 816 } 817 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 818 result = candidate; 819 } 820 return result; 821 } 822 823 } 824 825 /** Represents information about final local variable candidate. */ 826 private static class FinalVariableCandidate { 827 828 /** Identifier token. */ 829 private final DetailAST variableIdent; 830 /** Whether the variable is assigned. */ 831 private boolean assigned; 832 /** Whether the variable is already assigned. */ 833 private boolean alreadyAssigned; 834 835 /** 836 * Creates new instance. 837 * 838 * @param variableIdent variable identifier. 839 */ 840 /* package */ FinalVariableCandidate(DetailAST variableIdent) { 841 this.variableIdent = variableIdent; 842 } 843 844 } 845 846}