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.Arrays;
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 @FileStatefulCheck
128 public class FinalLocalVariableCheck extends AbstractCheck {
129
130
131
132
133
134 public static final String MSG_KEY = "final.variable";
135
136
137
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
160
161 private static final int[] LOOP_TYPES = {
162 TokenTypes.LITERAL_FOR,
163 TokenTypes.LITERAL_WHILE,
164 TokenTypes.LITERAL_DO,
165 };
166
167
168 private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
169
170
171 private final Deque<Deque<DetailAST>> prevScopeUninitializedVariables =
172 new ArrayDeque<>();
173
174
175 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables =
176 new ArrayDeque<>();
177
178
179
180
181
182
183 private boolean validateEnhancedForLoopVariable;
184
185 static {
186
187 Arrays.sort(ASSIGN_OPERATOR_TYPES);
188 Arrays.sort(LOOP_TYPES);
189 }
190
191
192
193
194
195
196
197 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) {
198 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
199 }
200
201 @Override
202 public int[] getRequiredTokens() {
203 return new int[] {
204 TokenTypes.IDENT,
205 TokenTypes.CTOR_DEF,
206 TokenTypes.METHOD_DEF,
207 TokenTypes.SLIST,
208 TokenTypes.OBJBLOCK,
209 TokenTypes.LITERAL_BREAK,
210 TokenTypes.LITERAL_FOR,
211 };
212 }
213
214 @Override
215 public int[] getDefaultTokens() {
216 return new int[] {
217 TokenTypes.IDENT,
218 TokenTypes.CTOR_DEF,
219 TokenTypes.METHOD_DEF,
220 TokenTypes.SLIST,
221 TokenTypes.OBJBLOCK,
222 TokenTypes.LITERAL_BREAK,
223 TokenTypes.LITERAL_FOR,
224 TokenTypes.VARIABLE_DEF,
225 };
226 }
227
228 @Override
229 public int[] getAcceptableTokens() {
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 TokenTypes.PARAMETER_DEF,
240 };
241 }
242
243
244
245 @Override
246 public void visitToken(DetailAST ast) {
247 switch (ast.getType()) {
248 case TokenTypes.OBJBLOCK:
249 case TokenTypes.METHOD_DEF:
250 case TokenTypes.CTOR_DEF:
251 case TokenTypes.LITERAL_FOR:
252 scopeStack.push(new ScopeData());
253 break;
254 case TokenTypes.SLIST:
255 currentScopeAssignedVariables.push(new ArrayDeque<>());
256 if (ast.getParent().getType() != TokenTypes.CASE_GROUP
257 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP)
258 == ast.getParent()) {
259 storePrevScopeUninitializedVariableData();
260 scopeStack.push(new ScopeData());
261 }
262 break;
263 case TokenTypes.PARAMETER_DEF:
264 if (!isInLambda(ast)
265 && ast.findFirstToken(TokenTypes.MODIFIERS)
266 .findFirstToken(TokenTypes.FINAL) == null
267 && !isInAbstractOrNativeMethod(ast)
268 && !ScopeUtil.isInInterfaceBlock(ast)
269 && !isMultipleTypeCatch(ast)
270 && !CheckUtil.isReceiverParameter(ast)) {
271 insertParameter(ast);
272 }
273 break;
274 case TokenTypes.VARIABLE_DEF:
275 if (ast.getParent().getType() != TokenTypes.OBJBLOCK
276 && ast.findFirstToken(TokenTypes.MODIFIERS)
277 .findFirstToken(TokenTypes.FINAL) == null
278 && !isVariableInForInit(ast)
279 && shouldCheckEnhancedForLoopVariable(ast)) {
280 insertVariable(ast);
281 }
282 break;
283 case TokenTypes.IDENT:
284 final int parentType = ast.getParent().getType();
285 if (isAssignOperator(parentType) && isFirstChild(ast)) {
286 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast);
287 if (candidate.isPresent()) {
288 determineAssignmentConditions(ast, candidate.get());
289 currentScopeAssignedVariables.peek().add(ast);
290 }
291 removeFinalVariableCandidateFromStack(ast);
292 }
293 break;
294 case TokenTypes.LITERAL_BREAK:
295 scopeStack.peek().containsBreak = true;
296 break;
297 default:
298 throw new IllegalStateException("Incorrect token type");
299 }
300 }
301
302 @Override
303 public void leaveToken(DetailAST ast) {
304 Map<String, FinalVariableCandidate> scope = null;
305 switch (ast.getType()) {
306 case TokenTypes.OBJBLOCK:
307 case TokenTypes.CTOR_DEF:
308 case TokenTypes.METHOD_DEF:
309 case TokenTypes.LITERAL_FOR:
310 scope = scopeStack.pop().scope;
311 break;
312 case TokenTypes.SLIST:
313
314
315 final Deque<DetailAST> prevScopeUninitializedVariableData =
316 prevScopeUninitializedVariables.peek();
317 boolean containsBreak = false;
318 if (ast.getParent().getType() != TokenTypes.CASE_GROUP
319 || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(),
320 TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) {
321 containsBreak = scopeStack.peek().containsBreak;
322 scope = scopeStack.pop().scope;
323 prevScopeUninitializedVariables.pop();
324 }
325 final DetailAST parent = ast.getParent();
326 if (containsBreak || shouldUpdateUninitializedVariables(parent)) {
327 updateAllUninitializedVariables(prevScopeUninitializedVariableData);
328 }
329 updateCurrentScopeAssignedVariables();
330 break;
331 default:
332
333 }
334 if (scope != null) {
335 for (FinalVariableCandidate candidate : scope.values()) {
336 final DetailAST ident = candidate.variableIdent;
337 log(ident, MSG_KEY, ident.getText());
338 }
339 }
340 }
341
342
343
344
345 private void updateCurrentScopeAssignedVariables() {
346
347 final Deque<DetailAST> poppedScopeAssignedVariableData =
348 currentScopeAssignedVariables.pop();
349 final Deque<DetailAST> currentScopeAssignedVariableData =
350 currentScopeAssignedVariables.peek();
351 if (currentScopeAssignedVariableData != null) {
352 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData);
353 }
354 }
355
356
357
358
359
360
361 private static void determineAssignmentConditions(DetailAST ident,
362 FinalVariableCandidate candidate) {
363 if (candidate.assigned) {
364 if (!isInSpecificCodeBlock(ident, TokenTypes.LITERAL_ELSE)
365 && !isInSpecificCodeBlock(ident, TokenTypes.CASE_GROUP)) {
366 candidate.alreadyAssigned = true;
367 }
368 }
369 else {
370 candidate.assigned = true;
371 }
372 }
373
374
375
376
377
378
379
380 private static boolean isInSpecificCodeBlock(DetailAST node, int blockType) {
381 boolean returnValue = false;
382 for (DetailAST token = node.getParent(); token != null; token = token.getParent()) {
383 final int type = token.getType();
384 if (type == blockType) {
385 returnValue = true;
386 break;
387 }
388 }
389 return returnValue;
390 }
391
392
393
394
395
396
397 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) {
398 Optional<FinalVariableCandidate> result = Optional.empty();
399 final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
400 while (iterator.hasNext() && !result.isPresent()) {
401 final ScopeData scopeData = iterator.next();
402 result = scopeData.findFinalVariableCandidateForAst(ast);
403 }
404 return result;
405 }
406
407
408
409
410 private void storePrevScopeUninitializedVariableData() {
411 final ScopeData scopeData = scopeStack.peek();
412 final Deque<DetailAST> prevScopeUninitializedVariableData =
413 new ArrayDeque<>();
414 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push);
415 prevScopeUninitializedVariables.push(prevScopeUninitializedVariableData);
416 }
417
418
419
420
421
422
423
424 private void updateAllUninitializedVariables(
425 Deque<DetailAST> prevScopeUninitializedVariableData) {
426
427 updateUninitializedVariables(prevScopeUninitializedVariableData);
428
429 prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables);
430 }
431
432
433
434
435
436 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) {
437 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator();
438 while (iterator.hasNext()) {
439 final DetailAST assignedVariable = iterator.next();
440 boolean shouldRemove = false;
441 for (DetailAST variable : scopeUninitializedVariableData) {
442 for (ScopeData scopeData : scopeStack) {
443 final FinalVariableCandidate candidate =
444 scopeData.scope.get(variable.getText());
445 DetailAST storedVariable = null;
446 if (candidate != null) {
447 storedVariable = candidate.variableIdent;
448 }
449 if (storedVariable != null
450 && isSameVariables(storedVariable, variable)
451 && isSameVariables(assignedVariable, variable)) {
452 scopeData.uninitializedVariables.push(variable);
453 shouldRemove = true;
454 }
455 }
456 }
457 if (shouldRemove) {
458 iterator.remove();
459 }
460 }
461 }
462
463
464
465
466
467
468
469 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) {
470 return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast);
471 }
472
473
474
475
476
477
478 private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) {
479 return ast.getType() == TokenTypes.LITERAL_IF
480 && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE;
481 }
482
483
484
485
486
487
488 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) {
489 return ast.getType() == TokenTypes.CASE_GROUP
490 && findLastChildWhichContainsSpecifiedToken(
491 ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast;
492 }
493
494
495
496
497
498
499
500
501
502 private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType,
503 int containType) {
504 DetailAST returnValue = null;
505 for (DetailAST astIterator = ast.getFirstChild(); astIterator != null;
506 astIterator = astIterator.getNextSibling()) {
507 if (astIterator.getType() == childType
508 && astIterator.findFirstToken(containType) != null) {
509 returnValue = astIterator;
510 }
511 }
512 return returnValue;
513 }
514
515
516
517
518
519
520 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
521 return validateEnhancedForLoopVariable
522 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
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 private void insertVariable(DetailAST ast) {
540 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope;
541 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
542 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode);
543
544 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE;
545 scope.put(astNode.getText(), candidate);
546 if (!isInitialized(astNode)) {
547 scopeStack.peek().uninitializedVariables.add(astNode);
548 }
549 }
550
551
552
553
554
555
556 private static boolean isInitialized(DetailAST ast) {
557 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN;
558 }
559
560
561
562
563
564
565 private static boolean isFirstChild(DetailAST ast) {
566 return ast.getPreviousSibling() == null;
567 }
568
569
570
571
572
573 private void removeFinalVariableCandidateFromStack(DetailAST ast) {
574 final Iterator<ScopeData> iterator = scopeStack.descendingIterator();
575 while (iterator.hasNext()) {
576 final ScopeData scopeData = iterator.next();
577 final Map<String, FinalVariableCandidate> scope = scopeData.scope;
578 final FinalVariableCandidate candidate = scope.get(ast.getText());
579 DetailAST storedVariable = null;
580 if (candidate != null) {
581 storedVariable = candidate.variableIdent;
582 }
583 if (storedVariable != null && isSameVariables(storedVariable, ast)) {
584 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) {
585 scope.remove(ast.getText());
586 }
587 break;
588 }
589 }
590 }
591
592
593
594
595
596
597 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) {
598 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE);
599 return typeAst.getFirstChild().getType() == TokenTypes.BOR;
600 }
601
602
603
604
605
606
607
608
609 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) {
610 boolean shouldRemove = true;
611 for (DetailAST variable : scopeData.uninitializedVariables) {
612 if (variable.getText().equals(ast.getText())) {
613
614
615
616 if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) {
617 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText());
618 shouldRemove = candidate.alreadyAssigned;
619 }
620 scopeData.uninitializedVariables.remove(variable);
621 break;
622 }
623 }
624 return shouldRemove;
625 }
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641 private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) {
642 DetailAST loop2 = variable.getParent();
643 while (loop2 != null
644 && !isLoopAst(loop2.getType())) {
645 loop2 = loop2.getParent();
646 }
647 return loop2 != null;
648 }
649
650
651
652
653
654
655 private static boolean isAssignOperator(int parentType) {
656 return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0;
657 }
658
659
660
661
662
663
664
665
666
667
668
669
670
671 private static boolean isVariableInForInit(DetailAST variableDef) {
672 return variableDef.getParent().getType() == TokenTypes.FOR_INIT;
673 }
674
675
676
677
678
679
680 private static boolean isInAbstractOrNativeMethod(DetailAST ast) {
681 boolean abstractOrNative = false;
682 DetailAST parent = ast.getParent();
683 while (parent != null && !abstractOrNative) {
684 if (parent.getType() == TokenTypes.METHOD_DEF) {
685 final DetailAST modifiers =
686 parent.findFirstToken(TokenTypes.MODIFIERS);
687 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null
688 || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null;
689 }
690 parent = parent.getParent();
691 }
692 return abstractOrNative;
693 }
694
695
696
697
698
699
700 private static boolean isInLambda(DetailAST paramDef) {
701 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA;
702 }
703
704
705
706
707
708
709 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) {
710 DetailAST astTraverse = ast;
711 while (astTraverse.getType() != TokenTypes.METHOD_DEF
712 && astTraverse.getType() != TokenTypes.CLASS_DEF
713 && astTraverse.getType() != TokenTypes.ENUM_DEF
714 && astTraverse.getType() != TokenTypes.CTOR_DEF
715 && !ScopeUtil.isClassFieldDef(astTraverse)) {
716 astTraverse = astTraverse.getParent();
717 }
718 return astTraverse;
719 }
720
721
722
723
724
725
726
727 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) {
728 final DetailAST classOrMethodOfAst1 =
729 findFirstUpperNamedBlock(ast1);
730 final DetailAST classOrMethodOfAst2 =
731 findFirstUpperNamedBlock(ast2);
732 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText());
733 }
734
735
736
737
738
739
740
741 private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) {
742 DetailAST loop1 = ast1.getParent();
743 while (loop1 != null && !isLoopAst(loop1.getType())) {
744 loop1 = loop1.getParent();
745 }
746 DetailAST loop2 = ast2.getParent();
747 while (loop2 != null && !isLoopAst(loop2.getType())) {
748 loop2 = loop2.getParent();
749 }
750 return loop1 != null && loop1 == loop2;
751 }
752
753
754
755
756
757
758 private static boolean isLoopAst(int ast) {
759 return Arrays.binarySearch(LOOP_TYPES, ast) >= 0;
760 }
761
762
763
764
765 private static class ScopeData {
766
767
768 private final Map<String, FinalVariableCandidate> scope = new HashMap<>();
769
770
771 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>();
772
773
774 private boolean containsBreak;
775
776
777
778
779
780
781 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) {
782 Optional<FinalVariableCandidate> result = Optional.empty();
783 DetailAST storedVariable = null;
784 final Optional<FinalVariableCandidate> candidate =
785 Optional.ofNullable(scope.get(ast.getText()));
786 if (candidate.isPresent()) {
787 storedVariable = candidate.get().variableIdent;
788 }
789 if (storedVariable != null && isSameVariables(storedVariable, ast)) {
790 result = candidate;
791 }
792 return result;
793 }
794
795 }
796
797
798 private static class FinalVariableCandidate {
799
800
801 private final DetailAST variableIdent;
802
803 private boolean assigned;
804
805 private boolean alreadyAssigned;
806
807
808
809
810
811 FinalVariableCandidate(DetailAST variableIdent) {
812 this.variableIdent = variableIdent;
813 }
814
815 }
816
817 }