/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.SwitchTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.Reachability;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.SwitchTree;
import com.sun.tools.javac.tree.JCTree;
import java.util.regex.Pattern;
/** @author cushon@google.com (Liam Miller-Cushon) */
@BugPattern(
name = "FallThrough",
altNames = "fallthrough",
category = JDK,
summary = "Switch case may fall through",
severity = ERROR
)
public class FallThrough extends BugChecker implements SwitchTreeMatcher {
private static final Pattern FALL_THROUGH_PATTERN =
Pattern.compile("\\bfalls?.?through\\b", Pattern.CASE_INSENSITIVE);
@Override
public Description matchSwitch(SwitchTree tree, VisitorState state) {
PeekingIterator<JCTree.JCCase> it =
Iterators.peekingIterator(((JCTree.JCSwitch) tree).cases.iterator());
while (it.hasNext()) {
JCTree.JCCase caseTree = it.next();
if (!it.hasNext()) {
break;
}
JCTree.JCCase next = it.peek();
if (caseTree.stats.isEmpty()) {
continue;
}
// We only care whether the last statement completes; javac would have already
// reported an error if that statement wasn't reachable, and the answer is
// independent of any preceding statements.
boolean completes = Reachability.canCompleteNormally(getLast(caseTree.stats));
String comments =
state
.getSourceCode()
.subSequence(caseEndPosition(state, caseTree), next.getStartPosition())
.toString()
.trim();
if (completes && !FALL_THROUGH_PATTERN.matcher(comments).find()) {
state.reportMatch(
buildDescription(next)
.setMessage(
"Execution may fall through from the previous case; add a `// fall through`"
+ " comment before this line if it was deliberate")
.build());
} else if (!completes && FALL_THROUGH_PATTERN.matcher(comments).find()) {
state.reportMatch(
buildDescription(next)
.setMessage(
"Switch case has 'fall through' comment, but execution cannot fall through"
+ " from the previous case")
.build());
}
}
return NO_MATCH;
}
private static int caseEndPosition(VisitorState state, JCTree.JCCase caseTree) {
// if the statement group is a single block statement, handle fall through comments at the
// end of the block
if (caseTree.stats.size() == 1) {
JCTree.JCStatement only = getOnlyElement(caseTree.stats);
if (only.hasTag(JCTree.Tag.BLOCK)) {
BlockTree blockTree = (BlockTree) only;
return blockTree.getStatements().isEmpty()
? ((JCTree) blockTree).getStartPosition()
: state.getEndPosition(getLast(blockTree.getStatements()));
}
}
return state.getEndPosition(caseTree);
}
}