1 | //////////////////////////////////////////////////////////////////////////////// | |
2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
3 | // Copyright (C) 2001-2018 the original author or authors. | |
4 | // | |
5 | // This library is free software; you can redistribute it and/or | |
6 | // modify it under the terms of the GNU Lesser General Public | |
7 | // License as published by the Free Software Foundation; either | |
8 | // version 2.1 of the License, or (at your option) any later version. | |
9 | // | |
10 | // This library is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | // Lesser General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU Lesser General Public | |
16 | // License along with this library; if not, write to the Free Software | |
17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
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 | * Check for ensuring that for loop control variables are not modified | |
38 | * inside the for block. An example is: | |
39 | * | |
40 | * <pre> | |
41 | * {@code | |
42 | * for (int i = 0; i < 1; i++) { | |
43 | * i++;//violation | |
44 | * } | |
45 | * } | |
46 | * </pre> | |
47 | * Rationale: If the control variable is modified inside the loop | |
48 | * body, the program flow becomes more difficult to follow.<br> | |
49 | * See <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14"> | |
50 | * FOR statement</a> specification for more details. | |
51 | * <p>Examples:</p> | |
52 | * | |
53 | * <pre> | |
54 | * <module name="ModifiedControlVariable"> | |
55 | * </module> | |
56 | * </pre> | |
57 | * | |
58 | * <p>Such loop would be suppressed: | |
59 | * | |
60 | * <pre> | |
61 | * {@code | |
62 | * for(int i=0; i < 10;) { | |
63 | * i++; | |
64 | * } | |
65 | * } | |
66 | * </pre> | |
67 | * | |
68 | * <p> | |
69 | * By default, This Check validates | |
70 | * <a href = "https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2"> | |
71 | * Enhanced For-Loop</a>. | |
72 | * </p> | |
73 | * <p> | |
74 | * Option 'skipEnhancedForLoopVariable' could be used to skip check of variable | |
75 | * from Enhanced For Loop. | |
76 | * </p> | |
77 | * <p> | |
78 | * An example of how to configure the check so that it skips enhanced For Loop Variable is: | |
79 | * </p> | |
80 | * <pre> | |
81 | * <module name="ModifiedControlVariable"> | |
82 | * <property name="skipEnhancedForLoopVariable" value="true"/> | |
83 | * </module> | |
84 | * </pre> | |
85 | * <p>Example:</p> | |
86 | * | |
87 | * <pre> | |
88 | * {@code | |
89 | * for (String line: lines) { | |
90 | * line = line.trim(); // it will skip this violation | |
91 | * } | |
92 | * } | |
93 | * </pre> | |
94 | * | |
95 | * | |
96 | * @author Daniel Grenner | |
97 | * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a> | |
98 | */ | |
99 | @FileStatefulCheck | |
100 | public final class ModifiedControlVariableCheck extends AbstractCheck { | |
101 | ||
102 | /** | |
103 | * A key is pointing to the warning message text in "messages.properties" | |
104 | * file. | |
105 | */ | |
106 | public static final String MSG_KEY = "modified.control.variable"; | |
107 | ||
108 | /** | |
109 | * Message thrown with IllegalStateException. | |
110 | */ | |
111 | private static final String ILLEGAL_TYPE_OF_TOKEN = "Illegal type of token: "; | |
112 | ||
113 | /** Operations which can change control variable in update part of the loop. */ | |
114 | private static final Set<Integer> MUTATION_OPERATIONS = | |
115 | Arrays.stream(new Integer[] { | |
116 | TokenTypes.POST_INC, | |
117 | TokenTypes.POST_DEC, | |
118 | TokenTypes.DEC, | |
119 | TokenTypes.INC, | |
120 | TokenTypes.ASSIGN, | |
121 | }).collect(Collectors.toSet()); | |
122 | ||
123 | /** Stack of block parameters. */ | |
124 | private final Deque<Deque<String>> variableStack = new ArrayDeque<>(); | |
125 | ||
126 | /** Controls whether to skip enhanced for-loop variable. */ | |
127 | private boolean skipEnhancedForLoopVariable; | |
128 | ||
129 | /** | |
130 | * Whether to skip enhanced for-loop variable or not. | |
131 | * @param skipEnhancedForLoopVariable whether to skip enhanced for-loop variable | |
132 | */ | |
133 | public void setSkipEnhancedForLoopVariable(boolean skipEnhancedForLoopVariable) { | |
134 | this.skipEnhancedForLoopVariable = skipEnhancedForLoopVariable; | |
135 | } | |
136 | ||
137 | @Override | |
138 | public int[] getDefaultTokens() { | |
139 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
140 | } | |
141 | ||
142 | @Override | |
143 | public int[] getRequiredTokens() { | |
144 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
145 | TokenTypes.OBJBLOCK, | |
146 | TokenTypes.LITERAL_FOR, | |
147 | TokenTypes.FOR_ITERATOR, | |
148 | TokenTypes.FOR_EACH_CLAUSE, | |
149 | TokenTypes.ASSIGN, | |
150 | TokenTypes.PLUS_ASSIGN, | |
151 | TokenTypes.MINUS_ASSIGN, | |
152 | TokenTypes.STAR_ASSIGN, | |
153 | TokenTypes.DIV_ASSIGN, | |
154 | TokenTypes.MOD_ASSIGN, | |
155 | TokenTypes.SR_ASSIGN, | |
156 | TokenTypes.BSR_ASSIGN, | |
157 | TokenTypes.SL_ASSIGN, | |
158 | TokenTypes.BAND_ASSIGN, | |
159 | TokenTypes.BXOR_ASSIGN, | |
160 | TokenTypes.BOR_ASSIGN, | |
161 | TokenTypes.INC, | |
162 | TokenTypes.POST_INC, | |
163 | TokenTypes.DEC, | |
164 | TokenTypes.POST_DEC, | |
165 | }; | |
166 | } | |
167 | ||
168 | @Override | |
169 | public int[] getAcceptableTokens() { | |
170 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
171 | } | |
172 | ||
173 | @Override | |
174 | public void beginTree(DetailAST rootAST) { | |
175 | // clear data | |
176 |
1
1. beginTree : removed call to java/util/Deque::clear → KILLED |
variableStack.clear(); |
177 | } | |
178 | ||
179 | @Override | |
180 | public void visitToken(DetailAST ast) { | |
181 | switch (ast.getType()) { | |
182 | case TokenTypes.OBJBLOCK: | |
183 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::enterBlock → KILLED |
enterBlock(); |
184 | break; | |
185 | case TokenTypes.LITERAL_FOR: | |
186 | case TokenTypes.FOR_ITERATOR: | |
187 | case TokenTypes.FOR_EACH_CLAUSE: | |
188 | //we need that Tokens only at leaveToken() | |
189 | break; | |
190 | case TokenTypes.ASSIGN: | |
191 | case TokenTypes.PLUS_ASSIGN: | |
192 | case TokenTypes.MINUS_ASSIGN: | |
193 | case TokenTypes.STAR_ASSIGN: | |
194 | case TokenTypes.DIV_ASSIGN: | |
195 | case TokenTypes.MOD_ASSIGN: | |
196 | case TokenTypes.SR_ASSIGN: | |
197 | case TokenTypes.BSR_ASSIGN: | |
198 | case TokenTypes.SL_ASSIGN: | |
199 | case TokenTypes.BAND_ASSIGN: | |
200 | case TokenTypes.BXOR_ASSIGN: | |
201 | case TokenTypes.BOR_ASSIGN: | |
202 | case TokenTypes.INC: | |
203 | case TokenTypes.POST_INC: | |
204 | case TokenTypes.DEC: | |
205 | case TokenTypes.POST_DEC: | |
206 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::checkIdent → KILLED |
checkIdent(ast); |
207 | break; | |
208 | default: | |
209 | throw new IllegalStateException(ILLEGAL_TYPE_OF_TOKEN + ast); | |
210 | } | |
211 | } | |
212 | ||
213 | @Override | |
214 | public void leaveToken(DetailAST ast) { | |
215 | switch (ast.getType()) { | |
216 | case TokenTypes.FOR_ITERATOR: | |
217 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::leaveForIter → KILLED |
leaveForIter(ast.getParent()); |
218 | break; | |
219 | case TokenTypes.FOR_EACH_CLAUSE: | |
220 |
1
1. leaveToken : negated conditional → KILLED |
if (!skipEnhancedForLoopVariable) { |
221 | final DetailAST paramDef = ast.findFirstToken(TokenTypes.VARIABLE_DEF); | |
222 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::leaveForEach → KILLED |
leaveForEach(paramDef); |
223 | } | |
224 | break; | |
225 | case TokenTypes.LITERAL_FOR: | |
226 |
1
1. leaveToken : negated conditional → KILLED |
if (!getCurrentVariables().isEmpty()) { |
227 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::leaveForDef → KILLED |
leaveForDef(ast); |
228 | } | |
229 | break; | |
230 | case TokenTypes.OBJBLOCK: | |
231 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::exitBlock → KILLED |
exitBlock(); |
232 | break; | |
233 | case TokenTypes.ASSIGN: | |
234 | case TokenTypes.PLUS_ASSIGN: | |
235 | case TokenTypes.MINUS_ASSIGN: | |
236 | case TokenTypes.STAR_ASSIGN: | |
237 | case TokenTypes.DIV_ASSIGN: | |
238 | case TokenTypes.MOD_ASSIGN: | |
239 | case TokenTypes.SR_ASSIGN: | |
240 | case TokenTypes.BSR_ASSIGN: | |
241 | case TokenTypes.SL_ASSIGN: | |
242 | case TokenTypes.BAND_ASSIGN: | |
243 | case TokenTypes.BXOR_ASSIGN: | |
244 | case TokenTypes.BOR_ASSIGN: | |
245 | case TokenTypes.INC: | |
246 | case TokenTypes.POST_INC: | |
247 | case TokenTypes.DEC: | |
248 | case TokenTypes.POST_DEC: | |
249 | //we need that Tokens only at visitToken() | |
250 | break; | |
251 | default: | |
252 | throw new IllegalStateException(ILLEGAL_TYPE_OF_TOKEN + ast); | |
253 | } | |
254 | } | |
255 | ||
256 | /** | |
257 | * Enters an inner class, which requires a new variable set. | |
258 | */ | |
259 | private void enterBlock() { | |
260 |
1
1. enterBlock : removed call to java/util/Deque::push → KILLED |
variableStack.push(new ArrayDeque<>()); |
261 | } | |
262 | ||
263 | /** | |
264 | * Leave an inner class, so restore variable set. | |
265 | */ | |
266 | private void exitBlock() { | |
267 | variableStack.pop(); | |
268 | } | |
269 | ||
270 | /** | |
271 | * Get current variable stack. | |
272 | * @return current variable stack | |
273 | */ | |
274 | private Deque<String> getCurrentVariables() { | |
275 |
1
1. getCurrentVariables : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getCurrentVariables to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return variableStack.peek(); |
276 | } | |
277 | ||
278 | /** | |
279 | * Check if ident is parameter. | |
280 | * @param ast ident to check. | |
281 | */ | |
282 | private void checkIdent(DetailAST ast) { | |
283 | final Deque<String> currentVariables = getCurrentVariables(); | |
284 |
2
1. checkIdent : negated conditional → KILLED 2. checkIdent : negated conditional → KILLED |
if (currentVariables != null && !currentVariables.isEmpty()) { |
285 | final DetailAST identAST = ast.getFirstChild(); | |
286 | ||
287 |
2
1. checkIdent : negated conditional → KILLED 2. checkIdent : negated conditional → KILLED |
if (identAST != null && identAST.getType() == TokenTypes.IDENT |
288 |
1
1. checkIdent : negated conditional → KILLED |
&& getCurrentVariables().contains(identAST.getText())) { |
289 |
1
1. checkIdent : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::log → KILLED |
log(ast.getLineNo(), ast.getColumnNo(), |
290 | MSG_KEY, identAST.getText()); | |
291 | } | |
292 | } | |
293 | } | |
294 | ||
295 | /** | |
296 | * Push current variables to the stack. | |
297 | * @param ast a for definition. | |
298 | */ | |
299 | private void leaveForIter(DetailAST ast) { | |
300 | final Set<String> variablesToPutInScope = getVariablesManagedByForLoop(ast); | |
301 | for (String variableName : variablesToPutInScope) { | |
302 |
1
1. leaveForIter : removed call to java/util/Deque::push → KILLED |
getCurrentVariables().push(variableName); |
303 | } | |
304 | } | |
305 | ||
306 | /** | |
307 | * Determines which variable are specific to for loop and should not be | |
308 | * change by inner loop body. | |
309 | * @param ast For Loop | |
310 | * @return Set of Variable Name which are managed by for | |
311 | */ | |
312 | private static Set<String> getVariablesManagedByForLoop(DetailAST ast) { | |
313 | final Set<String> initializedVariables = getForInitVariables(ast); | |
314 | final Set<String> iteratingVariables = getForIteratorVariables(ast); | |
315 |
1
1. getVariablesManagedByForLoop : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getVariablesManagedByForLoop to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return initializedVariables.stream().filter(iteratingVariables::contains) |
316 | .collect(Collectors.toSet()); | |
317 | } | |
318 | ||
319 | /** | |
320 | * Push current variables to the stack. | |
321 | * @param paramDef a for-each clause variable | |
322 | */ | |
323 | private void leaveForEach(DetailAST paramDef) { | |
324 | final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT); | |
325 |
1
1. leaveForEach : removed call to java/util/Deque::push → KILLED |
getCurrentVariables().push(paramName.getText()); |
326 | } | |
327 | ||
328 | /** | |
329 | * Pops the variables from the stack. | |
330 | * @param ast a for definition. | |
331 | */ | |
332 | private void leaveForDef(DetailAST ast) { | |
333 | final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT); | |
334 |
1
1. leaveForDef : negated conditional → KILLED |
if (forInitAST == null) { |
335 |
1
1. leaveForDef : negated conditional → KILLED |
if (!skipEnhancedForLoopVariable) { |
336 | // this is for-each loop, just pop variables | |
337 | getCurrentVariables().pop(); | |
338 | } | |
339 | } | |
340 | else { | |
341 | final Set<String> variablesManagedByForLoop = getVariablesManagedByForLoop(ast); | |
342 |
1
1. leaveForDef : removed call to com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::popCurrentVariables → KILLED |
popCurrentVariables(variablesManagedByForLoop.size()); |
343 | } | |
344 | } | |
345 | ||
346 | /** | |
347 | * Pops given number of variables from currentVariables. | |
348 | * @param count Count of variables to be popped from currentVariables | |
349 | */ | |
350 | private void popCurrentVariables(int count) { | |
351 |
3
1. popCurrentVariables : changed conditional boundary → KILLED 2. popCurrentVariables : Changed increment from 1 to -1 → KILLED 3. popCurrentVariables : negated conditional → KILLED |
for (int i = 0; i < count; i++) { |
352 | getCurrentVariables().pop(); | |
353 | } | |
354 | } | |
355 | ||
356 | /** | |
357 | * Get all variables initialized In init part of for loop. | |
358 | * @param ast for loop token | |
359 | * @return set of variables initialized in for loop | |
360 | */ | |
361 | private static Set<String> getForInitVariables(DetailAST ast) { | |
362 | final Set<String> initializedVariables = new HashSet<>(); | |
363 | final DetailAST forInitAST = ast.findFirstToken(TokenTypes.FOR_INIT); | |
364 | ||
365 | for (DetailAST parameterDefAST = forInitAST.findFirstToken(TokenTypes.VARIABLE_DEF); | |
366 |
1
1. getForInitVariables : negated conditional → KILLED |
parameterDefAST != null; |
367 | parameterDefAST = parameterDefAST.getNextSibling()) { | |
368 |
1
1. getForInitVariables : negated conditional → KILLED |
if (parameterDefAST.getType() == TokenTypes.VARIABLE_DEF) { |
369 | final DetailAST param = | |
370 | parameterDefAST.findFirstToken(TokenTypes.IDENT); | |
371 | ||
372 | initializedVariables.add(param.getText()); | |
373 | } | |
374 | } | |
375 |
1
1. getForInitVariables : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getForInitVariables to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return initializedVariables; |
376 | } | |
377 | ||
378 | /** | |
379 | * Get all variables which for loop iterating part change in every loop. | |
380 | * @param ast for loop literal(TokenTypes.LITERAL_FOR) | |
381 | * @return names of variables change in iterating part of for | |
382 | */ | |
383 | private static Set<String> getForIteratorVariables(DetailAST ast) { | |
384 | final Set<String> iteratorVariables = new HashSet<>(); | |
385 | final DetailAST forIteratorAST = ast.findFirstToken(TokenTypes.FOR_ITERATOR); | |
386 | final DetailAST forUpdateListAST = forIteratorAST.findFirstToken(TokenTypes.ELIST); | |
387 | ||
388 | findChildrenOfExpressionType(forUpdateListAST).stream() | |
389 | .filter(iteratingExpressionAST -> { | |
390 |
1
1. lambda$getForIteratorVariables$0 : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return MUTATION_OPERATIONS.contains(iteratingExpressionAST.getType()); |
391 |
1
1. getForIteratorVariables : removed call to java/util/stream/Stream::forEach → KILLED |
}).forEach(iteratingExpressionAST -> { |
392 | final DetailAST oneVariableOperatorChild = iteratingExpressionAST.getFirstChild(); | |
393 |
1
1. lambda$getForIteratorVariables$1 : negated conditional → KILLED |
if (oneVariableOperatorChild.getType() == TokenTypes.IDENT) { |
394 | iteratorVariables.add(oneVariableOperatorChild.getText()); | |
395 | } | |
396 | }); | |
397 | ||
398 |
1
1. getForIteratorVariables : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::getForIteratorVariables to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return iteratorVariables; |
399 | } | |
400 | ||
401 | /** | |
402 | * Find all child of given AST of type TokenType.EXPR | |
403 | * @param ast parent of expressions to find | |
404 | * @return all child of given ast | |
405 | */ | |
406 | private static List<DetailAST> findChildrenOfExpressionType(DetailAST ast) { | |
407 | final List<DetailAST> foundExpressions = new LinkedList<>(); | |
408 |
1
1. findChildrenOfExpressionType : negated conditional → KILLED |
if (ast != null) { |
409 | for (DetailAST iteratingExpressionAST = ast.findFirstToken(TokenTypes.EXPR); | |
410 |
1
1. findChildrenOfExpressionType : negated conditional → KILLED |
iteratingExpressionAST != null; |
411 | iteratingExpressionAST = iteratingExpressionAST.getNextSibling()) { | |
412 |
1
1. findChildrenOfExpressionType : negated conditional → KILLED |
if (iteratingExpressionAST.getType() == TokenTypes.EXPR) { |
413 | foundExpressions.add(iteratingExpressionAST.getFirstChild()); | |
414 | } | |
415 | } | |
416 | } | |
417 |
1
1. findChildrenOfExpressionType : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/ModifiedControlVariableCheck::findChildrenOfExpressionType to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return foundExpressions; |
418 | } | |
419 | ||
420 | } | |
Mutations | ||
139 |
1.1 |
|
144 |
1.1 |
|
170 |
1.1 |
|
176 |
1.1 |
|
183 |
1.1 |
|
206 |
1.1 |
|
217 |
1.1 |
|
220 |
1.1 |
|
222 |
1.1 |
|
226 |
1.1 |
|
227 |
1.1 |
|
231 |
1.1 |
|
260 |
1.1 |
|
275 |
1.1 |
|
284 |
1.1 2.2 |
|
287 |
1.1 2.2 |
|
288 |
1.1 |
|
289 |
1.1 |
|
302 |
1.1 |
|
315 |
1.1 |
|
325 |
1.1 |
|
334 |
1.1 |
|
335 |
1.1 |
|
342 |
1.1 |
|
351 |
1.1 2.2 3.3 |
|
366 |
1.1 |
|
368 |
1.1 |
|
375 |
1.1 |
|
390 |
1.1 |
|
391 |
1.1 |
|
393 |
1.1 |
|
398 |
1.1 |
|
408 |
1.1 |
|
410 |
1.1 |
|
412 |
1.1 |
|
417 |
1.1 |