1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayDeque;
23 import java.util.BitSet;
24 import java.util.Deque;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Optional;
29
30 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
31 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
34 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
35 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
36 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 @FileStatefulCheck
89 public class FinalLocalVariableCheck extends AbstractCheck {
90
91
92
93
94
95 public static final String MSG_KEY = "final.variable";
96
97
98
99
100 private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet(
101 TokenTypes.POST_INC,
102 TokenTypes.POST_DEC,
103 TokenTypes.ASSIGN,
104 TokenTypes.PLUS_ASSIGN,
105 TokenTypes.MINUS_ASSIGN,
106 TokenTypes.STAR_ASSIGN,
107 TokenTypes.DIV_ASSIGN,
108 TokenTypes.MOD_ASSIGN,
109 TokenTypes.SR_ASSIGN,
110 TokenTypes.BSR_ASSIGN,
111 TokenTypes.SL_ASSIGN,
112 TokenTypes.BAND_ASSIGN,
113 TokenTypes.BXOR_ASSIGN,
114 TokenTypes.BOR_ASSIGN,
115 TokenTypes.INC,
116 TokenTypes.DEC
117 );
118
119
120
121
122 private static final BitSet LOOP_TYPES = TokenUtil.asBitSet(
123 TokenTypes.LITERAL_FOR,
124 TokenTypes.LITERAL_WHILE,
125 TokenTypes.LITERAL_DO
126 );
127
128
129 private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
130
131
132 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables =
133 new ArrayDeque<>();
134
135
136
137
138
139
140 private boolean validateEnhancedForLoopVariable;
141
142
143
144
145
146
147 private boolean validateUnnamedVariables;
148
149
150
151
152
153
154
155
156
157 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
158 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
159 }
160
161
162
163
164
165
166
167
168
169 public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) {
170 this.validateUnnamedVariables = validateUnnamedVariables;
171 }
172
173 @Override
174 public int[] getRequiredTokens() {
175 return new int[] {
176 TokenTypes.IDENT,
177 TokenTypes.CTOR_DEF,
178 TokenTypes.METHOD_DEF,
179 TokenTypes.SLIST,
180 TokenTypes.OBJBLOCK,
181 TokenTypes.LITERAL_BREAK,
182 TokenTypes.LITERAL_FOR,
183 TokenTypes.EXPR,
184 };
185 }
186
187 @Override
188 public int[] getDefaultTokens() {
189 return new int[] {
190 TokenTypes.IDENT,
191 TokenTypes.CTOR_DEF,
192 TokenTypes.METHOD_DEF,
193 TokenTypes.SLIST,
194 TokenTypes.OBJBLOCK,
195 TokenTypes.LITERAL_BREAK,
196 TokenTypes.LITERAL_FOR,
197 TokenTypes.VARIABLE_DEF,
198 TokenTypes.EXPR,
199 };
200 }
201
202 @Override
203 public int[] getAcceptableTokens() {
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 TokenTypes.VARIABLE_DEF,
213 TokenTypes.PARAMETER_DEF,
214 TokenTypes.EXPR,
215 };
216 }
217
218
219
220 @Override
221 public void visitToken(DetailAST ast) {
222 switch (ast.getType()) {
223 case TokenTypes.OBJBLOCK:
224 case TokenTypes.METHOD_DEF:
225 case TokenTypes.CTOR_DEF:
226 case TokenTypes.LITERAL_FOR:
227 scopeStack.push(new ScopeData());
228 break;
229 case TokenTypes.SLIST:
230 currentScopeAssignedVariables.push(new ArrayDeque<>());
231 if (ast.getParent().getType() != TokenTypes.CASE_GROUP
232 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP)
233 == ast.getParent()) {
234 storePrevScopeUninitializedVariableData();
235 scopeStack.push(new ScopeData());
236 }
237 break;
238 case TokenTypes.PARAMETER_DEF:
239 if (!isInLambda(ast)
240 && ast.findFirstToken(TokenTypes.MODIFIERS)
241 .findFirstToken(TokenTypes.FINAL) == null
242 && !isInAbstractOrNativeMethod(ast)
243 && !ScopeUtil.isInInterfaceBlock(ast)
244 && !isMultipleTypeCatch(ast)
245 && !CheckUtil.isReceiverParameter(ast)) {
246 insertParameter(ast);
247 }
248 break;
249 case TokenTypes.VARIABLE_DEF:
250 if (ast.getParent().getType() != TokenTypes.OBJBLOCK
251 && ast.findFirstToken(TokenTypes.MODIFIERS)
252 .findFirstToken(TokenTypes.FINAL) == null
253 && !isVariableInForInit(ast)
254 && shouldCheckEnhancedForLoopVariable(ast)
255 && shouldCheckUnnamedVariable(ast)) {
256 insertVariable(ast);
257 }
258 break;
259 case TokenTypes.IDENT:
260 final int parentType = ast.getParent().getType();
261 if (isAssignOperator(parentType) && isFirstChild(ast)) {
262 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
263 if (candidate.isPresent()) {
264 determineAssignmentConditions(ast, candidate.orElseThrow());
265 currentScopeAssignedVariables.peek().add(ast);
266 }
267 removeFinalVariableCandidateFromStack(ast);
268 }
269 break;
270 case TokenTypes.LITERAL_BREAK:
271 scopeStack.peek().containsBreak = true;
272 break;
273 case TokenTypes.EXPR:
274
275 if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) {
276 storePrevScopeUninitializedVariableData();
277 }
278 break;
279 default:
280 throw new IllegalStateException("Incorrect token type");
281 }
282 }
283
284 @Override
285 public void leaveToken(DetailAST ast) {
286 Map<String, FinalVariableCandidate> scope = null;
287 final DetailAST parentAst = ast.getParent();
288 switch (ast.getType()) {
289 case TokenTypes.OBJBLOCK:
290 case TokenTypes.CTOR_DEF:
291 case TokenTypes.METHOD_DEF:
292 case TokenTypes.LITERAL_FOR:
293 scope = scopeStack.pop().scope;
294 break;
295 case TokenTypes.EXPR:
296
297 if (parentAst.getType() == TokenTypes.SWITCH_RULE
298 && shouldUpdateUninitializedVariables(parentAst)) {
299 updateAllUninitializedVariables();
300 }
301 break;
302 case TokenTypes.SLIST:
303 boolean containsBreak = false;
304 if (parentAst.getType() != TokenTypes.CASE_GROUP
305 || findLastCaseGroupWhichContainsSlist(parentAst.getParent()) == parentAst) {
306 containsBreak = scopeStack.peek().containsBreak;
307 scope = scopeStack.pop().scope;
308 }
309 if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) {
310 updateAllUninitializedVariables();
311 }
312 updateCurrentScopeAssignedVariables();
313 break;
314 default:
315
316 }
317 if (scope != null) {
318 for (FinalVariableCandidate candidate : scope.values()) {
319 final DetailAST ident = candidate.variableIdent;
320 log(ident, MSG_KEY, ident.getText());
321 }
322 }
323 }
324
325
326
327
328 private void updateCurrentScopeAssignedVariables() {
329
330 final Deque<DetailAST> poppedScopeAssignedVariableData =
331 currentScopeAssignedVariables.pop();
332 final Deque<DetailAST> currentScopeAssignedVariableData =
333 currentScopeAssignedVariables.peek();
334 if (currentScopeAssignedVariableData != null) {
335 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData);
336 }
337 }
338
339
340
341
342
343
344
345 private static void determineAssignmentConditions(DetailAST ident,
346 FinalVariableCandidate candidate) {
347 if (candidate.assigned) {
348 final int[] blockTypes = {
349 TokenTypes.LITERAL_ELSE,
350 TokenTypes.CASE_GROUP,
351 TokenTypes.SWITCH_RULE,
352 };
353 if (!isInSpecificCodeBlocks(ident, blockTypes)) {
354 candidate.alreadyAssigned = true;
355 }
356 }
357 else {
358 candidate.assigned = true;
359 }
360 }
361
362
363
364
365
366
367
368
369 private static boolean isInSpecificCodeBlocks(DetailAST node, int... blockTypes) {
370 boolean returnValue = false;
371 for (int blockType : blockTypes) {
372 for (DetailAST token = node; token != null; token = token.getParent()) {
373 final int type = token.getType();
374 if (type == blockType) {
375 returnValue = true;
376 break;
377 }
378 }
379 }
380 return returnValue;
381 }
382
383
384
385
386
387
388
389 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) {
390 Optional<FinalVariableCandidate> result = Optional.empty();
391 final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
392 while (iterator.hasNext() && result.isEmpty()) {
393 final ScopeData scopeData = iterator.next();
394 result = scopeData.findFinalVariableCandidateForAst(ast);
395 }
396 return result;
397 }
398
399
400
401
402 private void storePrevScopeUninitializedVariableData() {
403 final ScopeData scopeData = scopeStack.peek();
404 final Deque<DetailAST> prevScopeUninitializedVariableData =
405 new ArrayDeque<>();
406 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push);
407 scopeData.prevScopeUninitializedVariables = prevScopeUninitializedVariableData;
408 }
409
410
411
412
413 private void updateAllUninitializedVariables() {
414 final boolean hasSomeScopes = !currentScopeAssignedVariables.isEmpty();
415 if (hasSomeScopes) {
416 scopeStack.forEach(scopeData -> {
417 updateUninitializedVariables(scopeData.prevScopeUninitializedVariables);
418 });
419 }
420 }
421
422
423
424
425
426
427 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) {
428 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator();
429 while (iterator.hasNext()) {
430 final DetailAST assignedVariable = iterator.next();
431 boolean shouldRemove = false;
432 for (DetailAST variable : scopeUninitializedVariableData) {
433 for (ScopeData scopeData : scopeStack) {
434 final FinalVariableCandidate candidate =
435 scopeData.scope.get(variable.getText());
436 DetailAST storedVariable = null;
437 if (candidate != null) {
438 storedVariable = candidate.variableIdent;
439 }
440 if (storedVariable != null
441 && isSameVariables(assignedVariable, variable)) {
442 scopeData.uninitializedVariables.push(variable);
443 shouldRemove = true;
444 }
445 }
446 }
447 if (shouldRemove) {
448 iterator.remove();
449 }
450 }
451 }
452
453
454
455
456
457
458
459
460
461 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) {
462 return ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE
463 || isCaseTokenWithAnotherCaseFollowing(ast);
464 }
465
466
467
468
469
470
471
472
473 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) {
474 boolean result = false;
475 if (ast.getType() == TokenTypes.CASE_GROUP) {
476 result = findLastCaseGroupWhichContainsSlist(ast.getParent()) != ast;
477 }
478 else if (ast.getType() == TokenTypes.SWITCH_RULE) {
479 result = ast.getNextSibling().getType() == TokenTypes.SWITCH_RULE;
480 }
481 return result;
482 }
483
484
485
486
487
488
489
490
491 private static DetailAST findLastCaseGroupWhichContainsSlist(DetailAST literalSwitchAst) {
492 DetailAST returnValue = null;
493 for (DetailAST astIterator = literalSwitchAst.getFirstChild(); astIterator != null;
494 astIterator = astIterator.getNextSibling()) {
495 if (astIterator.findFirstToken(TokenTypes.SLIST) != null) {
496 returnValue = astIterator;
497 }
498 }
499 return returnValue;
500 }
501
502
503
504
505
506
507
508 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
509 return validateEnhancedForLoopVariable
510 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
511 }
512
513
514
515
516
517
518
519 private boolean shouldCheckUnnamedVariable(DetailAST ast) {
520 return validateUnnamedVariables
521 || !"_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
522 }
523
524
525
526
527
528
529 private void insertParameter(DetailAST ast) {
530 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
531 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
532 scope.put(astNode.getText(), new FinalVariableCandidate(astNode));
533 }
534
535
536
537
538
539
540 private void insertVariable(DetailAST ast) {
541 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
542 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
543 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode);
544
545 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE;
546 scope.put(astNode.getText(), candidate);
547 if (!isInitialized(astNode)) {
548 scopeStack.peek().uninitializedVariables.add(astNode);
549 }
550 }
551
552
553
554
555
556
557
558 private static boolean isInitialized(DetailAST ast) {
559 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN;
560 }
561
562
563
564
565
566
567
568 private static boolean isFirstChild(DetailAST ast) {
569 return ast.getPreviousSibling() == null;
570 }
571
572
573
574
575
576
577 private void removeFinalVariableCandidateFromStack(DetailAST ast) {
578 final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
579 while (iterator.hasNext()) {
580 final ScopeData scopeData = iterator.next();
581 final Map<String, FinalVariableCandidate> scope = scopeData.scope;
582 final FinalVariableCandidate candidate = scope.get(ast.getText());
583 DetailAST storedVariable = null;
584 if (candidate != null) {
585 storedVariable = candidate.variableIdent;
586 }
587 if (storedVariable != null && isSameVariables(storedVariable, ast)) {
588 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
589 scope.remove(ast.getText());
590 }
591 break;
592 }
593 }
594 }
595
596
597
598
599
600
601
602 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
603 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
604 return typeAst.findFirstToken(TokenTypes.BOR) != null;
605 }
606
607
608
609
610
611
612
613
614
615 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
616 boolean shouldRemove = true;
617 for (DetailAST variable : scopeData.uninitializedVariables) {
618 if (variable.getText().equals(ast.getText())) {
619
620
621
622 final DetailAST currAstLoopAstParent = getParentLoop(ast);
623 final DetailAST currVarLoopAstParent = getParentLoop(variable);
624 if (currAstLoopAstParent == currVarLoopAstParent) {
625 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
626 shouldRemove = candidate.alreadyAssigned;
627 }
628 scopeData.uninitializedVariables.remove(variable);
629 break;
630 }
631 }
632 return shouldRemove;
633 }
634
635
636
637
638
639
640
641
642
643 private static DetailAST getParentLoop(DetailAST ast) {
644 DetailAST parentLoop = ast;
645 while (parentLoop != null
646 && !isLoopAst(parentLoop.getType())) {
647 parentLoop = parentLoop.getParent();
648 }
649 return parentLoop;
650 }
651
652
653
654
655
656
657
658 private static boolean isAssignOperator(int parentType) {
659 return ASSIGN_OPERATOR_TYPES.get(parentType);
660 }
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676 private static boolean isVariableInForInit(DetailAST variableDef) {
677 return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
678 }
679
680
681
682
683
684
685
686 private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
687 boolean abstractOrNative = false;
688 DetailAST currentAst = ast;
689 while (currentAst != null && !abstractOrNative) {
690 if (currentAst.getType() == TokenTypes.METHOD_DEF) {
691 final DetailAST modifiers =
692 currentAst.findFirstToken(TokenTypes.MODIFIERS);
693 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null
694 || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null;
695 }
696 currentAst = currentAst.getParent();
697 }
698 return abstractOrNative;
699 }
700
701
702
703
704
705
706
707 private static boolean isInLambda(DetailAST paramDef) {
708 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
709 }
710
711
712
713
714
715
716
717 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
718 DetailAST astTraverse = ast;
719 while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF,
720 TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF)
721 && !ScopeUtil.isClassFieldDef(astTraverse)) {
722 astTraverse = astTraverse.getParent();
723 }
724 return astTraverse;
725 }
726
727
728
729
730
731
732
733
734 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
735 final DetailAST classOrMethodOfAst1 =
736 findFirstUpperNamedBlock(ast1);
737 final DetailAST classOrMethodOfAst2 =
738 findFirstUpperNamedBlock(ast2);
739 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText());
740 }
741
742
743
744
745
746
747
748 private static boolean isLoopAst(int ast) {
749 return LOOP_TYPES.get(ast);
750 }
751
752
753
754
755 private static final class ScopeData {
756
757
758 private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
759
760
761 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
762
763
764 private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>();
765
766
767 private boolean containsBreak;
768
769
770
771
772
773
774
775 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) {
776 Optional<FinalVariableCandidate> result = Optional.empty();
777 DetailAST storedVariable = null;
778 final Optional<FinalVariableCandidate> candidate =
779 Optional.ofNullable(scope.get(ast.getText()));
780 if (candidate.isPresent()) {
781 storedVariable = candidate.orElseThrow().variableIdent;
782 }
783 if (storedVariable != null && isSameVariables(storedVariable, ast)) {
784 result = candidate;
785 }
786 return result;
787 }
788
789 }
790
791
792 private static final class FinalVariableCandidate {
793
794
795 private final DetailAST variableIdent;
796
797 private boolean assigned;
798
799 private boolean alreadyAssigned;
800
801
802
803
804
805
806 private FinalVariableCandidate(DetailAST variableIdent) {
807 this.variableIdent = variableIdent;
808 }
809
810 }
811
812 }