package polyglot.visit; import java.util.*; import polyglot.ast.*; import polyglot.frontend.Job; import polyglot.types.SemanticException; import polyglot.types.TypeSystem; /** * Visitor which checks that all (terminating) paths through a * method must return. */ public class ExitChecker extends DataFlow { protected CodeDecl code; public ExitChecker(Job job, TypeSystem ts, NodeFactory nf) { super(job, ts, nf, false /* backward analysis */); } protected FlowGraph initGraph(CodeDecl code, Term root) { boolean returnsValue; this.code = code; if (code instanceof MethodDecl) { MethodDecl d = (MethodDecl) code; if (! d.methodInstance().returnType().isVoid()) { return super.initGraph(code, root); } } return null; } public Item createInitialItem(FlowGraph graph, Term node) { return DataFlowItem.EXITS; } protected static class DataFlowItem extends Item { final boolean exits; // whether all paths leaving this node lead to an exit protected DataFlowItem(boolean exits) { this.exits = exits; } public static final DataFlowItem EXITS = new DataFlowItem(true); public static final DataFlowItem DOES_NOT_EXIT = new DataFlowItem(false); public String toString() { return "exits=" + exits; } public boolean equals(Object o) { if (o instanceof DataFlowItem) { return this.exits == ((DataFlowItem)o).exits; } return false; } public int hashCode() { return (exits ? 5235 : 8673); } } public Map flow(Item in, FlowGraph graph, Term n, Set succEdgeKeys) { // If every path from the exit node to the entry goes through a return, // we're okay. So make the exit bit false at exit and true at every return; // the confluence operation is &&. // We deal with exceptions specially, and assume that any exception // edge to the exit node is OK. if (n instanceof Return) { return itemToMap(DataFlowItem.EXITS, succEdgeKeys); } if (n == graph.exitNode()) { // all exception edges to the exit node are regarded as exiting // correctly. Make sure non-exception edges have the // exit bit false. Map m = itemToMap(DataFlowItem.EXITS, succEdgeKeys); if (succEdgeKeys.contains(FlowGraph.EDGE_KEY_OTHER)) { m.put(FlowGraph.EDGE_KEY_OTHER, DataFlowItem.DOES_NOT_EXIT); } if (succEdgeKeys.contains(FlowGraph.EDGE_KEY_TRUE)) { m.put(FlowGraph.EDGE_KEY_TRUE, DataFlowItem.DOES_NOT_EXIT); } if (succEdgeKeys.contains(FlowGraph.EDGE_KEY_FALSE)) { m.put(FlowGraph.EDGE_KEY_FALSE, DataFlowItem.DOES_NOT_EXIT); } return m; } return itemToMap(in, succEdgeKeys); } public Item confluence(List inItems, Term node, FlowGraph graph) { // all paths must have an exit for (Iterator i = inItems.iterator(); i.hasNext(); ) { if (!((DataFlowItem)i.next()).exits) { return DataFlowItem.DOES_NOT_EXIT; } } return DataFlowItem.EXITS; } public void check(FlowGraph graph, Term n, Item inItem, Map outItems) throws SemanticException { // Check for statements not on the path to exit; compound // statements are allowed to be off the path. (e.g., "{ return; }" // or "while (true) S"). If a compound statement is truly // unreachable, one of its sub-statements will be also and we will // report an error there. if (n == graph.entryNode()) { if (outItems != null && !outItems.isEmpty()) { // due to the flow equations, all DataFlowItems in the outItems map // are the same, so just take the first one. DataFlowItem outItem = (DataFlowItem)outItems.values().iterator().next(); if (outItem != null && !outItem.exits) { throw new SemanticException("Missing return statement.", code.position()); } } } } }