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