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