/*
* Copyright 2013 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.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.BreakTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.ContinueTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.ReturnTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.ThrowTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.tools.javac.tree.JCTree.JCBreak;
import com.sun.tools.javac.tree.JCTree.JCContinue;
import com.sun.tools.javac.util.Name;
/**
* Matches the behaviour of javac's finally Xlint warning.
*
* 1) Any return statement in a finally block is an error
* 2) An uncaught throw statement in a finally block is an error. We can't always know whether a
* specific exception will be caught, so we report errors for throw statements that are not
* contained in a try with at least one catch block.
* 3) A continue statement in a finally block is an error if it breaks out of a (possibly labeled)
* loop that is outside the enclosing finally.
* 4) A break statement in a finally block is an error if it breaks out of a (possibly labeled)
* loop or a switch statement that is outside the enclosing finally.
*
* @author eaftan@google.com (Eddie Aftandilian)
* @author cushon@google.com (Liam Miller-Cushon)
*/
@BugPattern(
name = "Finally",
altNames = {"finally", "ThrowFromFinallyBlock"},
summary =
"If you return or throw from a finally, then values returned or thrown from the"
+ " try-catch block will be ignored. Consider using try-with-resources instead.",
category = JDK,
severity = WARNING,
generateExamplesFromTestCases = false
)
public class Finally extends BugChecker
implements ContinueTreeMatcher, ThrowTreeMatcher, BreakTreeMatcher, ReturnTreeMatcher {
@Override
public Description matchContinue(ContinueTree tree, VisitorState state) {
if (new FinallyJumpMatcher((JCContinue) tree).matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
@Override
public Description matchBreak(BreakTree tree, VisitorState state) {
if (new FinallyJumpMatcher((JCBreak) tree).matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
@Override
public Description matchThrow(ThrowTree tree, VisitorState state) {
if (new FinallyThrowMatcher().matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
@Override
public Description matchReturn(ReturnTree tree, VisitorState state) {
if (new FinallyCompletionMatcher<ReturnTree>().matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
private static enum MatchResult {
KEEP_LOOKING, NO_MATCH, FOUND_ERROR;
}
/**
* Base class for all finally matchers. Walks up the tree of enclosing statements and
* reports an error if it finds an enclosing finally block.
*
* @param <T> The type of the tree node to match against
*/
private static class FinallyCompletionMatcher<T extends StatementTree> implements Matcher<T> {
/**
* Matches a StatementTree type by walking that statement's ancestor chain.
* @returns true if an error is found.
*/
@Override
public boolean matches(T tree, VisitorState state) {
Tree prevTree = null;
for (Tree leaf : state.getPath()) {
switch (leaf.getKind()) {
case METHOD:
case LAMBDA_EXPRESSION:
case CLASS:
return false;
default:
break;
}
MatchResult mr = matchAncestor(leaf, prevTree);
if (mr != MatchResult.KEEP_LOOKING) {
return mr == MatchResult.FOUND_ERROR;
}
prevTree = leaf;
}
return false;
}
/**
* Match a tree in the ancestor chain given the ancestor's immediate descendant.
*/
protected MatchResult matchAncestor(Tree leaf, Tree prevTree) {
if (leaf instanceof TryTree) {
TryTree tryTree = (TryTree) leaf;
if (tryTree.getFinallyBlock() != null && tryTree.getFinallyBlock().equals(prevTree)) {
return MatchResult.FOUND_ERROR;
}
}
return MatchResult.KEEP_LOOKING;
}
}
/**
* Ancestor matcher for statements that break or continue out of a finally block.
*/
private static class FinallyJumpMatcher extends FinallyCompletionMatcher<StatementTree> {
private final Name label;
private final JumpType jumpType;
private enum JumpType {
BREAK,
CONTINUE
}
public FinallyJumpMatcher(JCContinue jcContinue) {
this.label = jcContinue.label;
this.jumpType = JumpType.CONTINUE;
}
public FinallyJumpMatcher(JCBreak jcBreak) {
this.label = jcBreak.label;
this.jumpType = JumpType.BREAK;
}
/**
* The target of a jump statement (break or continue) is
* (1) the enclosing loop if the jump is unlabeled
* (2) the enclosing LabeledStatementTree with matching label if the jump is labeled
* (3) the enclosing switch statement if the jump is a break
*
* If the target of a break or continue statement is encountered before reaching a finally
* block, return NO_MATCH.
*/
@Override
protected MatchResult matchAncestor(Tree leaf, Tree prevTree) {
// (1)
if (label == null) {
switch (leaf.getKind()) {
case WHILE_LOOP:
case DO_WHILE_LOOP:
case FOR_LOOP:
case ENHANCED_FOR_LOOP:
return MatchResult.NO_MATCH;
default:
break;
}
}
// (2)
if (label != null
&& leaf instanceof LabeledStatementTree
&& label.equals(((LabeledStatementTree) leaf).getLabel())) {
return MatchResult.NO_MATCH;
}
// (3)
if (jumpType == JumpType.BREAK && leaf instanceof SwitchTree) {
return MatchResult.NO_MATCH;
}
return super.matchAncestor(leaf, prevTree);
}
}
/** Match throw statements that are not caught. */
private static class FinallyThrowMatcher extends FinallyCompletionMatcher<ThrowTree> {
@Override
protected MatchResult matchAncestor(Tree tree, Tree prevTree) {
if (tree instanceof TryTree) {
TryTree tryTree = (TryTree) tree;
if (tryTree.getBlock().equals(prevTree) && !tryTree.getCatches().isEmpty()) {
// The current ancestor is a try block with associated catch blocks.
return MatchResult.NO_MATCH;
}
}
return super.matchAncestor(tree, prevTree);
}
}
}