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