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