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