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