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.ArrayList;
24 import java.util.BitSet;
25 import java.util.Deque;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30
31 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
32 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
33 import com.puppycrawl.tools.checkstyle.api.DetailAST;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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 @FileStatefulCheck
77 public final class ModifiedControlVariableCheck extends AbstractCheck {
78
79
80
81
82
83 public static final String MSG_KEY = "modified.control.variable";
84
85
86
87
88 private static final String ILLEGAL_TYPE_OF_TOKEN = "Illegal type of token: ";
89
90
91 private static final BitSet MUTATION_OPERATIONS = TokenUtil.asBitSet(
92 TokenTypes.POST_INC,
93 TokenTypes.POST_DEC,
94 TokenTypes.DEC,
95 TokenTypes.INC,
96 TokenTypes.ASSIGN);
97
98
99 private final Deque<Deque<String>> variableStack = new ArrayDeque<>();
100
101
102
103
104
105
106 private boolean skipEnhancedForLoopVariable;
107
108
109
110
111 public ModifiedControlVariableCheck() {
112
113 }
114
115
116
117
118
119
120
121
122
123 public void setSkipEnhancedForLoopVariable(boolean skipEnhancedForLoopVariable) {
124 this.skipEnhancedForLoopVariable = skipEnhancedForLoopVariable;
125 }
126
127 @Override
128 public int[] getDefaultTokens() {
129 return getRequiredTokens();
130 }
131
132 @Override
133 public int[] getRequiredTokens() {
134 return new int[] {
135 TokenTypes.OBJBLOCK,
136 TokenTypes.COMPACT_COMPILATION_UNIT,
137 TokenTypes.LITERAL_FOR,
138 TokenTypes.FOR_ITERATOR,
139 TokenTypes.FOR_EACH_CLAUSE,
140 TokenTypes.ASSIGN,
141 TokenTypes.PLUS_ASSIGN,
142 TokenTypes.MINUS_ASSIGN,
143 TokenTypes.STAR_ASSIGN,
144 TokenTypes.DIV_ASSIGN,
145 TokenTypes.MOD_ASSIGN,
146 TokenTypes.SR_ASSIGN,
147 TokenTypes.BSR_ASSIGN,
148 TokenTypes.SL_ASSIGN,
149 TokenTypes.BAND_ASSIGN,
150 TokenTypes.BXOR_ASSIGN,
151 TokenTypes.BOR_ASSIGN,
152 TokenTypes.INC,
153 TokenTypes.POST_INC,
154 TokenTypes.DEC,
155 TokenTypes.POST_DEC,
156 };
157 }
158
159 @Override
160 public int[] getAcceptableTokens() {
161 return getRequiredTokens();
162 }
163
164 @Override
165 public void beginTree(DetailAST rootAST) {
166
167 variableStack.clear();
168 }
169
170 @Override
171 public void visitToken(DetailAST ast) {
172 switch (ast.getType()) {
173 case TokenTypes.OBJBLOCK,
174 TokenTypes.COMPACT_COMPILATION_UNIT -> enterBlock();
175 case TokenTypes.LITERAL_FOR,
176 TokenTypes.FOR_ITERATOR,
177 TokenTypes.FOR_EACH_CLAUSE -> {
178
179 }
180 case TokenTypes.ASSIGN,
181 TokenTypes.PLUS_ASSIGN,
182 TokenTypes.MINUS_ASSIGN,
183 TokenTypes.STAR_ASSIGN,
184 TokenTypes.DIV_ASSIGN,
185 TokenTypes.MOD_ASSIGN,
186 TokenTypes.SR_ASSIGN,
187 TokenTypes.BSR_ASSIGN,
188 TokenTypes.SL_ASSIGN,
189 TokenTypes.BAND_ASSIGN,
190 TokenTypes.BXOR_ASSIGN,
191 TokenTypes.BOR_ASSIGN,
192 TokenTypes.INC,
193 TokenTypes.POST_INC,
194 TokenTypes.DEC,
195 TokenTypes.POST_DEC ->
196 checkIdent(ast);
197 default -> throw new IllegalStateException(ILLEGAL_TYPE_OF_TOKEN + ast);
198 }
199 }
200
201 @Override
202 public void leaveToken(DetailAST ast) {
203 switch (ast.getType()) {
204 case TokenTypes.FOR_ITERATOR -> leaveForIter(ast.getParent());
205 case TokenTypes.FOR_EACH_CLAUSE -> {
206 if (!skipEnhancedForLoopVariable) {
207 final DetailAST paramDef = ast.findFirstToken(TokenTypes.VARIABLE_DEF);
208 leaveForEach(paramDef);
209 }
210 }
211 case TokenTypes.LITERAL_FOR -> leaveForDef(ast);
212 case TokenTypes.OBJBLOCK,
213 TokenTypes.COMPACT_COMPILATION_UNIT -> exitBlock();
214 case TokenTypes.ASSIGN,
215 TokenTypes.PLUS_ASSIGN,
216 TokenTypes.MINUS_ASSIGN,
217 TokenTypes.STAR_ASSIGN,
218 TokenTypes.DIV_ASSIGN,
219 TokenTypes.MOD_ASSIGN,
220 TokenTypes.SR_ASSIGN,
221 TokenTypes.BSR_ASSIGN,
222 TokenTypes.SL_ASSIGN,
223 TokenTypes.BAND_ASSIGN,
224 TokenTypes.BXOR_ASSIGN,
225 TokenTypes.BOR_ASSIGN,
226 TokenTypes.INC,
227 TokenTypes.POST_INC,
228 TokenTypes.DEC,
229 TokenTypes.POST_DEC -> {
230
231 }
232 default -> throw new IllegalStateException(ILLEGAL_TYPE_OF_TOKEN + ast);
233 }
234 }
235
236
237
238
239 private void enterBlock() {
240 variableStack.push(new ArrayDeque<>());
241 }
242
243
244
245
246 private void exitBlock() {
247 variableStack.pop();
248 }
249
250
251
252
253
254
255 private Deque<String> getCurrentVariables() {
256 return variableStack.peek();
257 }
258
259
260
261
262
263
264 private void checkIdent(DetailAST ast) {
265 final Deque<String> currentVariables = getCurrentVariables();
266 final DetailAST identAST = ast.getFirstChild();
267
268 if (identAST != null && identAST.getType() == TokenTypes.IDENT
269 && currentVariables.contains(identAST.getText())) {
270 log(ast, MSG_KEY, identAST.getText());
271 }
272 }
273
274
275
276
277
278
279 private void leaveForIter(DetailAST ast) {
280 final Set<String> variablesToPutInScope = getVariablesManagedByForLoop(ast);
281 for (String variableName : variablesToPutInScope) {
282 getCurrentVariables().push(variableName);
283 }
284 }
285
286
287
288
289
290
291
292
293 private static Set<String> getVariablesManagedByForLoop(DetailAST ast) {
294 final Set<String> initializedVariables = getForInitVariables(ast);
295 final Set<String> iteratingVariables = getForIteratorVariables(ast);
296 return initializedVariables.stream().filter(iteratingVariables::contains)
297 .collect(Collectors.toUnmodifiableSet());
298 }
299
300
301
302
303
304
305 private void leaveForEach(DetailAST paramDef) {
306
307
308 final boolean isRecordPattern = paramDef == null;
309
310 if (!isRecordPattern) {
311 final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
312 getCurrentVariables().push(paramName.getText());
313 }
314 }
315
316
317
318
319
320
321 private void leaveForDef(DetailAST ast) {
322 final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT);
323 if (forInitAST == null) {
324 final Deque<String> currentVariables = getCurrentVariables();
325 if (!skipEnhancedForLoopVariable && !currentVariables.isEmpty()) {
326
327 currentVariables.pop();
328 }
329 }
330 else {
331 final Set<String> variablesManagedByForLoop = getVariablesManagedByForLoop(ast);
332 popCurrentVariables(variablesManagedByForLoop.size());
333 }
334 }
335
336
337
338
339
340
341 private void popCurrentVariables(int count) {
342 for (int i = 0; i < count; i++) {
343 getCurrentVariables().pop();
344 }
345 }
346
347
348
349
350
351
352
353 private static Set<String> getForInitVariables(DetailAST ast) {
354 final Set<String> initializedVariables = new HashSet<>();
355 final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT);
356
357 for (DetailAST parameterDefAST = forInitAST.findFirstToken(TokenTypes.VARIABLE_DEF);
358 parameterDefAST != null;
359 parameterDefAST = parameterDefAST.getNextSibling()) {
360 if (parameterDefAST.getType() == TokenTypes.VARIABLE_DEF) {
361 final DetailAST param =
362 parameterDefAST.findFirstToken(TokenTypes.IDENT);
363
364 initializedVariables.add(param.getText());
365 }
366 }
367 return initializedVariables;
368 }
369
370
371
372
373
374
375
376 private static Set<String> getForIteratorVariables(DetailAST ast) {
377 final Set<String> iteratorVariables = new HashSet<>();
378 final DetailAST forIteratorAST = ast.findFirstToken(TokenTypes.FOR_ITERATOR);
379 final DetailAST forUpdateListAST = forIteratorAST.findFirstToken(TokenTypes.ELIST);
380
381 findChildrenOfExpressionType(forUpdateListAST).stream()
382 .filter(iteratingExpressionAST -> {
383 return MUTATION_OPERATIONS.get(iteratingExpressionAST.getType());
384 }).forEach(iteratingExpressionAST -> {
385 final DetailAST oneVariableOperatorChild = iteratingExpressionAST.getFirstChild();
386 iteratorVariables.add(oneVariableOperatorChild.getText());
387 });
388
389 return iteratorVariables;
390 }
391
392
393
394
395
396
397
398 private static List<DetailAST> findChildrenOfExpressionType(DetailAST ast) {
399 final List<DetailAST> foundExpressions = new ArrayList<>();
400 if (ast != null) {
401 for (DetailAST iteratingExpressionAST = ast.findFirstToken(TokenTypes.EXPR);
402 iteratingExpressionAST != null;
403 iteratingExpressionAST = iteratingExpressionAST.getNextSibling()) {
404 if (iteratingExpressionAST.getType() == TokenTypes.EXPR) {
405 foundExpressions.add(iteratingExpressionAST.getFirstChild());
406 }
407 }
408 }
409 return foundExpressions;
410 }
411
412 }