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.HashSet;
23  import java.util.Objects;
24  import java.util.Optional;
25  import java.util.Set;
26  import java.util.regex.Pattern;
27  import java.util.stream.Stream;
28  
29  import com.puppycrawl.tools.checkstyle.StatelessCheck;
30  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31  import com.puppycrawl.tools.checkstyle.api.DetailAST;
32  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34  
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  @StatelessCheck
61  public class FallThroughCheck extends AbstractCheck {
62  
63      
64  
65  
66  
67      public static final String MSG_FALL_THROUGH = "fall.through";
68  
69      
70  
71  
72  
73      public static final String MSG_FALL_THROUGH_LAST = "fall.through.last";
74  
75      
76      private boolean checkLastCaseGroup;
77  
78      
79  
80  
81  
82      private Pattern reliefPattern = Pattern.compile("falls?[ -]?thr(u|ough)");
83  
84      @Override
85      public int[] getDefaultTokens() {
86          return getRequiredTokens();
87      }
88  
89      @Override
90      public int[] getRequiredTokens() {
91          return new int[] {TokenTypes.CASE_GROUP};
92      }
93  
94      @Override
95      public int[] getAcceptableTokens() {
96          return getRequiredTokens();
97      }
98  
99      @Override
100     public boolean isCommentNodesRequired() {
101         return true;
102     }
103 
104     
105 
106 
107 
108 
109 
110 
111 
112     public void setReliefPattern(Pattern pattern) {
113         reliefPattern = pattern;
114     }
115 
116     
117 
118 
119 
120 
121 
122     public void setCheckLastCaseGroup(boolean value) {
123         checkLastCaseGroup = value;
124     }
125 
126     @Override
127     public void visitToken(DetailAST ast) {
128         final DetailAST nextGroup = ast.getNextSibling();
129         final boolean isLastGroup = nextGroup.getType() != TokenTypes.CASE_GROUP;
130         if (!isLastGroup || checkLastCaseGroup) {
131             final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
132 
133             if (slist != null && !isTerminated(slist, true, true, new HashSet<>())
134                     && !hasFallThroughComment(ast)) {
135                 if (isLastGroup) {
136                     log(ast, MSG_FALL_THROUGH_LAST);
137                 }
138                 else {
139                     log(nextGroup, MSG_FALL_THROUGH);
140                 }
141             }
142         }
143     }
144 
145     
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157     private boolean isTerminated(final DetailAST ast, boolean useBreak,
158                                  boolean useContinue, Set<String> labelsForCurrentSwitchScope) {
159 
160         return switch (ast.getType()) {
161             case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
162                     TokenTypes.LITERAL_THROW -> true;
163             case TokenTypes.LITERAL_BREAK -> useBreak
164                     || hasLabel(ast, labelsForCurrentSwitchScope);
165             case TokenTypes.LITERAL_CONTINUE -> useContinue
166                     || hasLabel(ast, labelsForCurrentSwitchScope);
167             case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
168                     labelsForCurrentSwitchScope);
169             case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
170                     labelsForCurrentSwitchScope);
171             case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
172                 checkLoop(ast, labelsForCurrentSwitchScope);
173             case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
174                     labelsForCurrentSwitchScope);
175             case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
176                     labelsForCurrentSwitchScope);
177             case TokenTypes.LITERAL_SYNCHRONIZED ->
178                 checkSynchronized(ast, useBreak, useContinue,
179                     labelsForCurrentSwitchScope);
180             case TokenTypes.LABELED_STAT -> {
181                 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
182                 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
183                         labelsForCurrentSwitchScope);
184             }
185             default -> false;
186         };
187     }
188 
189     
190 
191 
192 
193 
194 
195 
196     private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
197         return Optional.ofNullable(statement)
198                 .map(DetailAST::getFirstChild)
199                 .filter(child -> child.getType() == TokenTypes.IDENT)
200                 .map(DetailAST::getText)
201                 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
202                 .isPresent();
203     }
204 
205     
206 
207 
208 
209 
210 
211 
212 
213 
214 
215     private boolean checkSlist(final DetailAST slistAst, boolean useBreak,
216                                boolean useContinue, Set<String> labels) {
217         DetailAST lastStmt = slistAst.getLastChild();
218 
219         if (lastStmt.getType() == TokenTypes.RCURLY) {
220             lastStmt = lastStmt.getPreviousSibling();
221         }
222 
223         while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
224                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
225             lastStmt = lastStmt.getPreviousSibling();
226         }
227 
228         return lastStmt != null
229             && isTerminated(lastStmt, useBreak, useContinue, labels);
230     }
231 
232     
233 
234 
235 
236 
237 
238 
239 
240 
241 
242     private boolean checkIf(final DetailAST ast, boolean useBreak,
243                             boolean useContinue, Set<String> labels) {
244         final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
245 
246         final DetailAST elseStmt = getNextNonCommentAst(thenStmt);
247 
248         return elseStmt != null
249                 && isTerminated(thenStmt, useBreak, useContinue, labels)
250                 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
251     }
252 
253     
254 
255 
256 
257 
258 
259     private static DetailAST getNextNonCommentAst(DetailAST ast) {
260         DetailAST nextSibling = ast.getNextSibling();
261         while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
262                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
263             nextSibling = nextSibling.getNextSibling();
264         }
265         return nextSibling;
266     }
267 
268     
269 
270 
271 
272 
273 
274 
275 
276     private boolean checkLoop(final DetailAST ast, Set<String> labels) {
277         final DetailAST loopBody;
278         if (ast.getType() == TokenTypes.LITERAL_DO) {
279             final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
280             loopBody = lparen.getPreviousSibling();
281         }
282         else {
283             final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
284             loopBody = rparen.getNextSibling();
285         }
286         return isTerminated(loopBody, false, false, labels);
287     }
288 
289     
290 
291 
292 
293 
294 
295 
296 
297 
298 
299     private boolean checkTry(final DetailAST ast, boolean useBreak,
300                              boolean useContinue, Set<String> labels) {
301         final DetailAST finalStmt = ast.getLastChild();
302         boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
303                 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
304                 useBreak, useContinue, labels);
305 
306         if (!isTerminated) {
307             DetailAST firstChild = ast.getFirstChild();
308 
309             if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
310                 firstChild = firstChild.getNextSibling();
311             }
312 
313             isTerminated = isTerminated(firstChild,
314                     useBreak, useContinue, labels);
315 
316             DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
317             while (catchStmt != null
318                     && isTerminated
319                     && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
320                 final DetailAST catchBody =
321                         catchStmt.findFirstToken(TokenTypes.SLIST);
322                 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
323                 catchStmt = catchStmt.getNextSibling();
324             }
325         }
326         return isTerminated;
327     }
328 
329     
330 
331 
332 
333 
334 
335 
336 
337 
338     private boolean checkSwitch(DetailAST literalSwitchAst,
339                                 boolean useContinue, Set<String> labels) {
340         DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
341         boolean isTerminated = caseGroup != null;
342         while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
343             final DetailAST caseBody =
344                 caseGroup.findFirstToken(TokenTypes.SLIST);
345             isTerminated = caseBody != null
346                     && isTerminated(caseBody, false, useContinue, labels);
347             caseGroup = caseGroup.getNextSibling();
348         }
349         return isTerminated;
350     }
351 
352     
353 
354 
355 
356 
357 
358 
359 
360 
361 
362     private boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
363                                       boolean useContinue, Set<String> labels) {
364         return isTerminated(
365             synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
366     }
367 
368     
369 
370 
371 
372 
373 
374 
375 
376 
377 
378 
379 
380 
381 
382 
383 
384 
385 
386 
387 
388 
389 
390 
391 
392 
393     private boolean hasFallThroughComment(DetailAST currentCase) {
394         final DetailAST nextSibling = currentCase.getNextSibling();
395         final DetailAST ast;
396         if (nextSibling.getType() == TokenTypes.CASE_GROUP) {
397             ast = nextSibling.getFirstChild();
398         }
399         else {
400             ast = currentCase;
401         }
402         return hasReliefComment(ast);
403     }
404 
405     
406 
407 
408 
409 
410 
411     private boolean hasReliefComment(DetailAST ast) {
412         final DetailAST nonCommentAst = getNextNonCommentAst(ast);
413         boolean result = false;
414         if (nonCommentAst != null) {
415             final int prevLineNumber = nonCommentAst.getPreviousSibling().getLineNo();
416             result = Stream.iterate(nonCommentAst.getPreviousSibling(),
417                             Objects::nonNull,
418                             DetailAST::getPreviousSibling)
419                     .takeWhile(sibling -> sibling.getLineNo() == prevLineNumber)
420                     .map(DetailAST::getFirstChild)
421                     .filter(Objects::nonNull)
422                     .anyMatch(firstChild -> reliefPattern.matcher(firstChild.getText()).find());
423         }
424         return result;
425     }
426 
427 }