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