/* * Copyright 2014 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.refaster; import static com.google.errorprone.refaster.ControlFlowVisitor.Result.ALWAYS_RETURNS; import static com.google.errorprone.refaster.ControlFlowVisitor.Result.MAY_BREAK_OR_RETURN; import static com.google.errorprone.refaster.ControlFlowVisitor.Result.NEVER_EXITS; import com.google.errorprone.refaster.ControlFlowVisitor.BreakContext; import com.google.errorprone.refaster.ControlFlowVisitor.Result; import com.sun.source.tree.BlockTree; import com.sun.source.tree.BreakTree; import com.sun.source.tree.CaseTree; import com.sun.source.tree.CatchTree; import com.sun.source.tree.ContinueTree; import com.sun.source.tree.DoWhileLoopTree; import com.sun.source.tree.EnhancedForLoopTree; import com.sun.source.tree.ExpressionStatementTree; import com.sun.source.tree.ForLoopTree; import com.sun.source.tree.IfTree; 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.SynchronizedTree; import com.sun.source.tree.ThrowTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TryTree; import com.sun.source.tree.WhileLoopTree; import com.sun.source.util.SimpleTreeVisitor; import java.util.HashSet; import java.util.Set; import javax.lang.model.element.Name; /** * Analyzes a series of statements to determine whether they don't, sometimes, or never return. * * @author lowasser@google.com (Louis Wasserman) */ class ControlFlowVisitor extends SimpleTreeVisitor<Result, BreakContext> { public static final ControlFlowVisitor INSTANCE = new ControlFlowVisitor(); /** * The state of whether a sequence of statements may return, break out of the visited statements, * or neither. */ enum Result { NEVER_EXITS { @Override Result or(Result other) { switch (other) { case MAY_BREAK_OR_RETURN: case NEVER_EXITS: return other; default: return MAY_RETURN; } } @Override Result then(Result other) { return other; } }, MAY_BREAK_OR_RETURN { @Override Result or(Result other) { return MAY_BREAK_OR_RETURN; } @Override Result then(Result other) { return MAY_BREAK_OR_RETURN; } }, MAY_RETURN { @Override Result or(Result other) { return (other == MAY_BREAK_OR_RETURN) ? MAY_BREAK_OR_RETURN : MAY_RETURN; } @Override Result then(Result other) { switch (other) { case MAY_BREAK_OR_RETURN: case ALWAYS_RETURNS: return other; default: return MAY_RETURN; } } }, ALWAYS_RETURNS { @Override Result or(Result other) { switch (other) { case MAY_BREAK_OR_RETURN: case ALWAYS_RETURNS: return other; default: return MAY_RETURN; } } @Override Result then(Result other) { return ALWAYS_RETURNS; } }; abstract Result or(Result other); abstract Result then(Result other); } static class BreakContext { final Set<Name> internalLabels; int loopDepth; private BreakContext() { this.internalLabels = new HashSet<>(); this.loopDepth = 0; } void enter(Name label) { internalLabels.add(label); } void exit(Name label) { internalLabels.remove(label); } } private ControlFlowVisitor() {} public Result visitStatement(StatementTree node) { return node.accept(this, new BreakContext()); } public Result visitStatements(Iterable<? extends StatementTree> nodes) { return visitStatements(nodes, new BreakContext()); } private Result visitStatements(Iterable<? extends StatementTree> nodes, BreakContext cxt) { Result result = NEVER_EXITS; for (StatementTree node : nodes) { result = result.then(node.accept(this, cxt)); } return result; } @Override protected Result defaultAction(Tree node, BreakContext cxt) { return NEVER_EXITS; } @Override public Result visitBlock(BlockTree node, BreakContext cxt) { return visitStatements(node.getStatements(), cxt); } @Override public Result visitDoWhileLoop(DoWhileLoopTree node, BreakContext cxt) { cxt.loopDepth++; try { return node.getStatement().accept(this, cxt).or(NEVER_EXITS); } finally { cxt.loopDepth--; } } @Override public Result visitWhileLoop(WhileLoopTree node, BreakContext cxt) { cxt.loopDepth++; try { return node.getStatement().accept(this, cxt).or(NEVER_EXITS); } finally { cxt.loopDepth--; } } @Override public Result visitForLoop(ForLoopTree node, BreakContext cxt) { cxt.loopDepth++; try { return node.getStatement().accept(this, cxt).or(NEVER_EXITS); } finally { cxt.loopDepth--; } } @Override public Result visitEnhancedForLoop(EnhancedForLoopTree node, BreakContext cxt) { cxt.loopDepth++; try { return node.getStatement().accept(this, cxt).or(NEVER_EXITS); } finally { cxt.loopDepth--; } } @Override public Result visitSwitch(SwitchTree node, BreakContext cxt) { Result result = null; boolean seenDefault = false; cxt.loopDepth++; try { for (CaseTree caseTree : node.getCases()) { if (caseTree.getExpression() == null) { seenDefault = true; } if (result == null) { result = caseTree.accept(this, cxt); } else { result = result.or(caseTree.accept(this, cxt)); } } if (!seenDefault) { result = result.or(NEVER_EXITS); } return result; } finally { cxt.loopDepth--; } } @Override public Result visitCase(CaseTree node, BreakContext cxt) { return visitStatements(node.getStatements(), cxt); } @Override public Result visitSynchronized(SynchronizedTree node, BreakContext cxt) { return node.getBlock().accept(this, cxt); } @Override public Result visitTry(TryTree node, BreakContext cxt) { Result result = node.getBlock().accept(this, cxt); for (CatchTree catchTree : node.getCatches()) { result = result.or(catchTree.accept(this, cxt)); } if (node.getFinallyBlock() != null) { result = result.then(node.getFinallyBlock().accept(this, cxt)); } return result; } @Override public Result visitCatch(CatchTree node, BreakContext cxt) { return node.getBlock().accept(this, cxt); } @Override public Result visitIf(IfTree node, BreakContext cxt) { Result thenResult = node.getThenStatement().accept(this, cxt); Result elseResult = (node.getElseStatement() == null) ? NEVER_EXITS : node.getElseStatement().accept(this, cxt); return thenResult.or(elseResult); } @Override public Result visitExpressionStatement(ExpressionStatementTree node, BreakContext cxt) { return NEVER_EXITS; } @Override public Result visitLabeledStatement(LabeledStatementTree node, BreakContext cxt) { cxt.enter(node.getLabel()); try { return node.getStatement().accept(this, cxt); } finally { cxt.exit(node.getLabel()); } } @Override public Result visitBreak(BreakTree node, BreakContext cxt) { if (cxt.internalLabels.contains(node.getLabel()) || (node.getLabel() == null && cxt.loopDepth > 0)) { return NEVER_EXITS; } else { return MAY_BREAK_OR_RETURN; } } @Override public Result visitContinue(ContinueTree node, BreakContext cxt) { if (cxt.internalLabels.contains(node.getLabel()) || (node.getLabel() == null && cxt.loopDepth > 0)) { return NEVER_EXITS; } else { return MAY_BREAK_OR_RETURN; } } @Override public Result visitReturn(ReturnTree node, BreakContext cxt) { return ALWAYS_RETURNS; } @Override public Result visitThrow(ThrowTree node, BreakContext cxt) { return ALWAYS_RETURNS; } }