// Copyright (C) 2008 Google Inc. // // 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.caja.ancillary.linter; import com.google.caja.parser.ParseTreeNode; import com.google.caja.parser.js.AssignOperation; import com.google.caja.parser.js.Block; import com.google.caja.parser.js.BreakStmt; import com.google.caja.parser.js.CaseStmt; import com.google.caja.parser.js.CatchStmt; import com.google.caja.parser.js.Conditional; import com.google.caja.parser.js.ContinueStmt; import com.google.caja.parser.js.DebuggerStmt; import com.google.caja.parser.js.Declaration; import com.google.caja.parser.js.DefaultCaseStmt; import com.google.caja.parser.js.DirectivePrologue; import com.google.caja.parser.js.DoWhileLoop; import com.google.caja.parser.js.Expression; import com.google.caja.parser.js.ExpressionStmt; import com.google.caja.parser.js.FinallyStmt; import com.google.caja.parser.js.ForEachLoop; import com.google.caja.parser.js.ForLoop; import com.google.caja.parser.js.FormalParam; import com.google.caja.parser.js.FunctionConstructor; import com.google.caja.parser.js.FunctionDeclaration; import com.google.caja.parser.js.Identifier; import com.google.caja.parser.js.LabeledStatement; import com.google.caja.parser.js.LabeledStmtWrapper; import com.google.caja.parser.js.MultiDeclaration; import com.google.caja.parser.js.Noop; import com.google.caja.parser.js.ObjProperty; import com.google.caja.parser.js.ObjectConstructor; import com.google.caja.parser.js.Operation; import com.google.caja.parser.js.Reference; import com.google.caja.parser.js.ReturnStmt; import com.google.caja.parser.js.Statement; import com.google.caja.parser.js.SwitchStmt; import com.google.caja.parser.js.ThrowStmt; import com.google.caja.parser.js.TryStmt; import com.google.caja.parser.js.WhileLoop; import com.google.caja.parser.js.WithStmt; import com.google.caja.util.SyntheticAttributeKey; import java.util.List; /** * Given a variable definition and a set of uses, computes the liveness of * that variable. * * <p> * This class is implemented by doing the same kinds of tree based computations * that EcmaScript 262 uses to define the semantics of the various control * statements and expressions, so many of the comments below are expressed in * terms of conservative conclusions about the ways in which a hypothetical * parse-tree interpreter would process a program. * * <h2>Glossary</h2> * See also definitions in {@link LexicalScope}. * <dl> * <dt>{@link LiveSet Liveness}</dt> * <dd>At a point in a program the set of symbols that must have been assigned * a value and not subsequently gone out of scope before control could * reach that point in the program.</dd> * <dt>{@link ExitModes Exit Modes}</dt> * <dd>The ways in which control leaves a node. The code below assumes that * an exception can be thrown by any statement or expression except, so * only explicit {@code throw} statements are used in calculations. * This assumption simplifies processing of all structures except * {@code try} blocks. * <dt>Normal exit</dt> * <dd>When control leaves a scope as a result of a * {@link ReturnStmt <tt>return</tt>} statement or a block completing. * A program that does not halt neither exits normally nor abnormally, * though this analyzer cannot detect all infinite loops, and so assumes * that any loop condition will eventually return false.</dd> * <dd>Abrupt exit</dt> * <dd>When control leaves a scope as a result of an exception.</dd> * </dl> * * <p> * The {@link #liveness} method dispatches on node * type to methods that recurse to walk the tree. The result of this * calculation at each node is a {@link LiveCalc} object which contains * a {@link LiveSet set of live variables}, computed conservatively, and an * {@link ExitModes exit-mode} object that is analogous to the * <tt>(normal, empty, empty)</tt> style triples used in chapter 12 of * ES262 to define statement semantics. * * @author mikesamuel@gmail.com */ final class VariableLiveness { /** Visible for testing. */ static final SyntheticAttributeKey<LiveSet> LIVENESS = new SyntheticAttributeKey<LiveSet>(LiveSet.class, "liveness"); /** * Returns the set of symbols that must be live when control enters the * given node. * @return null if {@link #calculateLiveness} has not been called on any * AST containing this node or if control never enters this node. * Control does not enter some leaf nodes such as {@link Identifier}s * and in unreachable code as in {@code foo(); return; unreachable();}. */ static LiveSet livenessFor(ParseTreeNode node) { return node.getAttributes().get(LIVENESS); } /** * Compute liveness for the given tree. * @param node a Javascript AST that has been annotated with scopes as by a * {@link ScopeAnalyzer}. * @return a pair describing the ways in which control leaves node. * If the input node were to exit normally, then {@link LiveCalc#vars} * would be the set of variables live once execution finished. * If there are {@code break}s that match no labeled loop, then those * will be visible via {@link LiveCalc#exits}. */ static LiveCalc calculateLiveness(ParseTreeNode node) { return liveness(node, new LiveSet(node)); } /** * Attaches liveness info to node and recurses to children. * @param onEntry the set of variables live when controls enters the given * node. */ private static LiveCalc liveness(ParseTreeNode node, LiveSet onEntry) { node.getAttributes().set(LIVENESS, onEntry); LiveCalc onExit; if (node instanceof Statement) { onExit = processStatement((Statement) node, onEntry); // If a break or continue reaches here, take it into account by // stopping propagation of exit mode info associate with its label and // incorporating liveness information at the point the break or continue // occurred. if (node instanceof LabeledStatement) { onExit = processLabel(((LabeledStatement) node), onExit); } } else if (node instanceof Expression) { // Because of our simplifying assumptions about all statements and // expressions possibly throwing exceptions, we can treat all expressions // as if they complete. onExit = new LiveCalc( processExpression((Expression) node, onEntry), ExitModes.COMPLETES); } else { // We can't handle parse tree nodes we don't recognizer. If this occurs, // either the input isn't a valid JS tree, or a node-type handler needs // to be added. throw new RuntimeException(node.getClass().getName()); } onExit = onExit.filter(ScopeAnalyzer.containingScopeForNode(node)); return onExit; } private static LiveCalc processStatement(Statement s, LiveSet onEntry) { if (s instanceof Block) { return processBlock((Block) s, onEntry); } else if (s instanceof Conditional) { return processConditional((Conditional) s, onEntry); } else if (s instanceof DoWhileLoop) { return processDoWhileLoop((DoWhileLoop) s, onEntry); } else if (s instanceof WhileLoop) { return processWhileLoop((WhileLoop) s, onEntry); } else if (s instanceof ForLoop) { return processForLoop((ForLoop) s, onEntry); } else if (s instanceof ForEachLoop) { return processForEachLoop((ForEachLoop) s, onEntry); } else if (s instanceof ExpressionStmt) { return processExpressionStmt((ExpressionStmt) s, onEntry); } else if (s instanceof FunctionDeclaration) { return processFunctionDeclaration((FunctionDeclaration) s, onEntry); } else if (s instanceof Declaration) { return processDeclaration((Declaration) s, onEntry); } else if (s instanceof MultiDeclaration) { return processMultiDeclaration((MultiDeclaration) s, onEntry); } else if (s instanceof SwitchStmt) { return processSwitchStmt((SwitchStmt) s, onEntry); } else if (s instanceof DefaultCaseStmt) { return processDefaultCaseStmt((DefaultCaseStmt) s, onEntry); } else if (s instanceof CaseStmt) { return processCaseStmt((CaseStmt) s, onEntry); } else if (s instanceof TryStmt) { return processTryStmt((TryStmt) s, onEntry); } else if (s instanceof CatchStmt) { return processCatchStmt((CatchStmt) s, onEntry); } else if (s instanceof FinallyStmt) { return processFinallyStmt((FinallyStmt) s, onEntry); } else if (s instanceof BreakStmt) { return processBreakStmt((BreakStmt) s, onEntry); } else if (s instanceof ContinueStmt) { return processContinueStmt((ContinueStmt) s, onEntry); } else if (s instanceof ReturnStmt) { return processReturnStmt((ReturnStmt) s, onEntry); } else if (s instanceof ThrowStmt) { return processThrowStmt((ThrowStmt) s, onEntry); } else if (s instanceof WithStmt) { return processWithStmt((WithStmt) s, onEntry); } else if (s instanceof Noop) { return processNoop(onEntry); } else if (s instanceof LabeledStmtWrapper) { return processLabeledStmtWrapper((LabeledStmtWrapper) s, onEntry); } else if (s instanceof DirectivePrologue) { return processDirectivePrologue(onEntry); } else if (s instanceof DebuggerStmt) { return new LiveCalc(onEntry, ExitModes.COMPLETES); } else { throw new RuntimeException(s.getClass().getName()); } } /** * Process an expression, computing the set of variables live after the * expression executes, but ignoring exit modes. * We don't currently track exit modes through expressions though, since they * can't return break or continue, and almost any expression can raise an * exception. */ private static LiveSet processExpression(Expression e, LiveSet onEntry) { // We may want to reconsider tracking exceptions specifically through // expressions, to more accurately model // try { // return (function () { throw new Error; })(); // } catch (ex) { // a = 1; // } // // a is provably live here if (e instanceof Operation) { return processOperation((Operation) e, onEntry); } else if (e instanceof FunctionConstructor) { // Returns the live-set at the end of the body. // Process that to fill out the information stored in the parse tree, // but don't use the result since the function has not been executed yet. processFunctionConstructor((FunctionConstructor) e); return onEntry; } else if (e instanceof Reference) { return processReference((Reference) e, onEntry); } else { return processLiteralOrConstructor(e, onEntry); } } private static LiveCalc processBlock(Block b, LiveSet onEntry) { LiveCalc onExit = new LiveCalc(onEntry, ExitModes.COMPLETES); for (Statement child : b.children()) { if (child instanceof FunctionDeclaration) { onExit = liveness(child, onExit.vars); } } for (Statement child : b.children()) { if (child instanceof FunctionDeclaration) { continue; } LiveCalc r = liveness(child, onExit.vars); if (r.exits.completes()) { // union for side-effects in series. onExit = new LiveCalc( r.vars.union(onExit.vars), r.exits.union(onExit.exits)); } else { // once the block is exited, stop processing unreachable code onExit = r.withExits(onExit.exits.union(r.exits)); } if (!onExit.exits.completes()) { break; } } return onExit; } private static LiveCalc processConditional(Conditional c, LiveSet onEntry) { List<? extends ParseTreeNode> children = c.children(); LiveSet afterLastCond = onEntry; LiveCalc onExit = null; for (int i = 0, n = children.size(); i <= n; i += 2) { LiveCalc afterClause; if (i != n) { // There is a clause to process. if (i + 1 < n) { // Process if/else if. // We are not processing an else clause, or the implicit else; at the // end of a conditional. ConditionalLiveSet afterCond = processCondition( (Expression) children.get(i), afterLastCond); afterClause = liveness(children.get(i + 1), afterCond.truthy); afterLastCond = afterCond.falsey; } else { // Process the else clause afterClause = liveness(children.get(i), afterLastCond); } } else { // Pretend there's an else; to make sure exit modes are correct. afterClause = new LiveCalc(afterLastCond, ExitModes.COMPLETES); } if (onExit == null) { onExit = afterClause; } else { onExit = new LiveCalc( (afterClause.exits.completes() ? onExit.vars.intersection(afterClause.vars) : onExit.vars), afterClause.exits.intersection(onExit.exits)); } } assert onExit != null; return onExit; } private static LiveCalc processDoWhileLoop(DoWhileLoop dw, LiveSet onEntry) { // label for this loop and others consumed in processLabel LiveCalc postBody = liveness(dw.getBody(), onEntry); LiveSet condVars; if (postBody.exits.completes() || postBody.exits.atContinue("") != null || postBody.exits.atContinue(dw.getLabel()) != null) { // If the body always exits without a continue, the condition is // unreachable code. // do-while loops are the only ones that behave this way. condVars = processCondition(dw.getCondition(), postBody.vars).falsey; } else { condVars = postBody.vars; } return postBody.withVars(condVars); } private static LiveCalc processWhileLoop(WhileLoop s, LiveSet onEntry) { ConditionalLiveSet postCond = processCondition(s.getCondition(), onEntry); LiveCalc postBody = liveness(s.getBody(), postCond.truthy); return postBody // If the condition is falsey, then the body never executed, so // the vars set are the intersection of the two cases .withVars(postCond.falsey.intersection(postBody.vars)) // And the exit modes are the intersection of the body modes, and // the case where the statement completes because the condition was // initially false. .withExits(postBody.exits.intersection(ExitModes.COMPLETES)); } private static LiveCalc processForLoop(ForLoop loop, LiveSet onEntry) { LiveCalc postInit = liveness(loop.getInitializer(), onEntry); ConditionalLiveSet postCond = processCondition( loop.getCondition(), postInit.vars); LiveCalc postBody = liveness(loop.getBody(), postCond.truthy); if (postBody.exits.completes()) { // If the body completes, then the loop increment is reachable. liveness(loop.getIncrement(), postBody.vars); } return postBody .withVars( postBody.exits.completes() ? postBody.vars.intersection(postCond.falsey) : postCond.falsey) // Unless the condition is provably false, then it completes. .withExits(postBody.exits.intersection(ExitModes.COMPLETES)); } private static LiveCalc processForEachLoop(ForEachLoop s, LiveSet onEntry) { LiveCalc postObj = liveness(s.getContainer(), onEntry); Statement receiver = s.getKeyReceiver(); LiveCalc preBody = liveness(s.getKeyReceiver(), postObj.vars); preBody = preBody.withExits(preBody.exits.union(postObj.exits)); if (receiver instanceof Declaration) { // Define any variable that holds the key. preBody = preBody.withVars( preBody.vars.with((Declaration) receiver)); } else { preBody = preBody.withVars( preBody.vars.with( (Reference) ((ExpressionStmt) receiver).getExpression())); } LiveCalc postBody = liveness(s.getBody(), preBody.vars); return postBody.withVars(postBody.vars.intersection(postObj.vars)); } private static LiveCalc processExpressionStmt( ExpressionStmt es, LiveSet onEntry) { return liveness(es.getExpression(), onEntry); } private static LiveCalc processFunctionDeclaration( FunctionDeclaration fd, LiveSet onEntry) { liveness(fd.getInitializer(), onEntry); return new LiveCalc(onEntry.with(fd), ExitModes.COMPLETES); } private static LiveCalc processDeclaration(Declaration d, LiveSet onEntry) { if (d.getInitializer() == null) { // Declarations without initializers do not make their variable live. return new LiveCalc(onEntry, ExitModes.COMPLETES); } LiveCalc postInit = liveness(d.getInitializer(), onEntry); return new LiveCalc(postInit.vars.with(d), postInit.exits); } private static LiveCalc processMultiDeclaration( MultiDeclaration md, LiveSet onEntry) { LiveCalc onExit = new LiveCalc(onEntry, ExitModes.COMPLETES); for (Declaration d : md.children()) { LiveCalc r = liveness(d, onExit.vars); onExit = new LiveCalc( r.vars.union(onExit.vars), r.exits.union(onExit.exits)); } return onExit; } private static LiveCalc processSwitchStmt(SwitchStmt s, LiveSet onEntry) { List<? extends ParseTreeNode> children = s.children(); LiveCalc postSwitchValue = liveness(children.get(0), onEntry); boolean sawDefault = false; LiveCalc last = null; for (int i = 1, n = children.size(); i < n; ++i) { ParseTreeNode node = children.get(i); if (node instanceof DefaultCaseStmt) { sawDefault = true; } ExitModes exits = last == null ? null : last.exits; last = liveness(node, postSwitchValue.vars); if (exits != null) { last = last.withExits(last.exits.intersection(exits)); } } if (sawDefault) { return last; } else if (last == null) { // no case statements return postSwitchValue; } else { return new LiveCalc( postSwitchValue.vars.intersection(last.vars), postSwitchValue.exits.intersection(last.exits)); } } private static LiveCalc processDefaultCaseStmt( DefaultCaseStmt s, LiveSet onEntry) { return liveness(s.getBody(), onEntry); } private static LiveCalc processCaseStmt(CaseStmt s, LiveSet onEntry) { LiveCalc postMatch = liveness(s.getCaseValue(), onEntry); return liveness(s.getBody(), postMatch.vars); } private static LiveCalc processTryStmt(TryStmt s, LiveSet onEntry) { LiveCalc postBody = liveness(s.getBody(), onEntry); // Compute the set of live variables before any finally statement. LiveCalc preFinally; boolean hasFinally = s.getFinallyClause() != null; if (s.getCatchClause() != null) { boolean postBodyReturnsAbruptly = postBody.exits.returnsAbruptly(); postBody = postBody.withExits(postBody.exits.withoutAbruptReturn()); LiveCalc postCatch = liveness( s.getCatchClause(), // The body may not have executed completely, so do the intersection // with the beginning. // We cannot use information about the vars associated with throws // clauses even if postBody.exits.returnAbruptly, since an exception // may have been raised by the interpreter before control reached // a throw clause. // Even in // try { throw new Error(a = 1); } catch (ex) { ... } // we do not know if a is live in the catch, because an exception // may have been raised on the reference to Error before a = 1 is // evaluated. onEntry.intersection(postBody.vars)); if (!hasFinally && postBody.exits.returnsNormally()) { preFinally = postCatch; } else if (!hasFinally && postCatch.exits.returns()) { preFinally = postBody; } else if (!postCatch.exits.completes()) { // Any return or break from the catch clause is trumped by a finally // block. preFinally = hasFinally ? new LiveCalc( onEntry, postBody.exits.intersection(postCatch.exits)) : postBody; } else if (postBodyReturnsAbruptly || !postBody.exits.completes()) { preFinally = postCatch; } else { preFinally = new LiveCalc( postBody.vars.intersection(postCatch.vars), ExitModes.COMPLETES); } } else { // Since there is no catch statement, there must be a finally, assert hasFinally; // so control will reach the finally block from the body regardless // of the body exit modes. preFinally = new LiveCalc(onEntry, ExitModes.COMPLETES); } if (hasFinally) { // finally always executes, so its results dominate return liveness(s.getFinallyClause(), preFinally.vars); } return preFinally; } private static LiveCalc processCatchStmt(CatchStmt s, LiveSet onEntry) { liveness(s.getException(), onEntry); return liveness(s.getBody(), onEntry.with(s.getException())); } private static LiveCalc processFinallyStmt(FinallyStmt s, LiveSet onEntry) { return liveness(s.getBody(), onEntry); } private static LiveCalc processBreakStmt(BreakStmt s, LiveSet onEntry) { return new LiveCalc(onEntry, ExitModes.COMPLETES.withBreak(s, onEntry)); } private static LiveCalc processContinueStmt(ContinueStmt s, LiveSet onEntry) { return new LiveCalc(onEntry, ExitModes.COMPLETES.withContinue(s, onEntry)); } private static LiveCalc processReturnStmt(ReturnStmt rs, LiveSet onEntry) { LiveSet onExit; if (rs.getReturnValue() != null) { LiveCalc r = liveness(rs.getReturnValue(), onEntry); if (r.exits.returnsAbruptly()) { // If the result of evaluating the return value consistently threw an // exception as in // return (function () { throw new Error; })() // that should dominate. return r; } onExit = r.vars; } else { onExit = onEntry; } return new LiveCalc( onExit, ExitModes.COMPLETES.withNormalReturn(rs, onExit)); } private static LiveCalc processThrowStmt(ThrowStmt ts, LiveSet onEntry) { LiveCalc onExit = liveness(ts.getException(), onEntry); return onExit.withExits(onExit.exits.withAbruptReturn(ts, onExit.vars)); } private static LiveCalc processWithStmt(WithStmt ts, LiveSet onEntry) { LiveCalc afterObject = liveness(ts.getScopeObject(), onEntry); // We can't draw any conclusions about code under a with statement. // Not even about code in closures inside it. // Process the body and throw out everything but the exit modes. LiveCalc afterBody = liveness(ts.getBody(), afterObject.vars); scrub(ts.getBody()); return afterObject.withExits(afterBody.exits); } private static void scrub(ParseTreeNode node) { node.getAttributes().remove(LIVENESS); for (ParseTreeNode child : node.children()) { scrub(child); } } private static LiveCalc processNoop(LiveSet onEntry) { return new LiveCalc(onEntry, ExitModes.COMPLETES); } private static LiveCalc processLabeledStmtWrapper( LabeledStmtWrapper s, LiveSet onEntry) { // label consumed in processLabel return liveness(s.getBody(), onEntry); } private static LiveSet processOperation(Operation op, LiveSet onEntry) { List<? extends Expression> operands = op.children(); switch (op.getOperator()) { case TERNARY: ConditionalLiveSet postCond = processLogicOperand(op, 0, onEntry); LiveCalc postThen = liveness(operands.get(1), postCond.truthy); LiveCalc postElse = liveness(operands.get(2), postCond.falsey); return postThen.vars.intersection(postElse.vars); case LOGICAL_AND: case LOGICAL_OR: ConditionalLiveSet p = processCondition(op, onEntry); return p.truthy.intersection(p.falsey); case FUNCTION_CALL: if (operands.get(0) instanceof FunctionConstructor) { return processImmediatelyCalledFunction(op, onEntry); } break; default: break; } LiveSet postOperand = onEntry; for (Expression operand : operands) { postOperand = liveness(operand, postOperand).vars; } if (op instanceof AssignOperation && operands.get(0) instanceof Reference) { postOperand = postOperand.with((Reference) operands.get(0)); } return postOperand; } /** * Do case based analysis of conditions so that conditional branches can * incorporate the fact that different variables may be live when an * expression returns a truthy value than when it returns a falsey one. */ private static ConditionalLiveSet processCondition(Expression e, LiveSet onEntry) { // This branch is different from the others in that it recurses without // calling liveness(), so we have to be careful to store LIVENESS info here // too. e.getAttributes().set(LIVENESS, onEntry); if (e instanceof Operation) { Operation op = (Operation) e; switch (op.getOperator()) { case LOGICAL_AND: { ConditionalLiveSet left = processLogicOperand(op, 0, onEntry); ConditionalLiveSet right = processLogicOperand(op, 1, left.truthy); return new ConditionalLiveSet( // It is truthy if both the left and right are truthy left.truthy.union(right.truthy), // It is falsey if either the left or right are falsey left.falsey.intersection(right.falsey)); } case LOGICAL_OR: { ConditionalLiveSet left = processLogicOperand(op, 0, onEntry); ConditionalLiveSet right = processLogicOperand(op, 1, left.falsey); return new ConditionalLiveSet( // It is truthy if either the left or right are truthy left.truthy.intersection(right.truthy), // It is falsey if both the left and right evaluate are falsey left.falsey.union(right.falsey)); } case NOT: return processLogicOperand(op, 0, onEntry).inverse(); default: break; } } LiveSet ls = processExpression(e, onEntry); return new ConditionalLiveSet(ls, ls); } private static ConditionalLiveSet processLogicOperand( Operation op, int opIdx, LiveSet onEntry) { return processCondition(op.children().get(opIdx), onEntry); } private static LiveSet processImmediatelyCalledFunction( Operation op, LiveSet onEntry) { List<? extends Expression> operands = op.children(); FunctionConstructor fn = (FunctionConstructor) operands.get(0); // operands are resolved before the function body is executed LiveSet onExit = onEntry; for (Expression actual : operands.subList(1, operands.size())) { onExit = liveness(actual, onExit).vars; } LiveSet asAResultOfCallingFn = processFunctionConstructor(fn); return onExit.union(asAResultOfCallingFn); } /** * @return the live-set at the end of the function body. Unlike other * process methods, this does not return the live-set as a result of * processing the declaration. */ private static LiveSet processFunctionConstructor(FunctionConstructor fc) { // Process the function body, but do not the result since liveness does // not extend across function boundaries. LiveSet fnBodyDefs = new LiveSet(fc); for (FormalParam formal : fc.getParams()) { fnBodyDefs = fnBodyDefs.with(formal); } return liveness(fc.getBody(), fnBodyDefs).vars; } /** @param r unused. */ private static LiveSet processReference(Reference r, LiveSet onEntry) { return onEntry; } private static LiveSet processLiteralOrConstructor( Expression e, LiveSet onEntry) { LiveSet last = onEntry; if (e instanceof ObjectConstructor) { for (ObjProperty p : ((ObjectConstructor) e).children()) { last = liveness(p.children().get(1), last).vars; } } else { for (ParseTreeNode child : e.children()) { Expression childE = (Expression) child; last = liveness(childE, last).vars; } } return last; } private static LiveCalc processDirectivePrologue(LiveSet onEntry) { return new LiveCalc(onEntry, ExitModes.COMPLETES); } /** * Incorporates the set of variables live at breaks into the live-set and * updates exit modes to take into account the way execution continues from * a labeled statement that is the target of a reachable {@code break}. */ private static LiveCalc processLabel(LabeledStatement s, LiveCalc onExit) { String label = s.getLabel(); LiveSet ls = onExit.vars; ExitModes em = onExit.exits; ExitModes.ExitMode breakSet = em.atBreak(label); if (breakSet != null) { if (breakSet.always) { // If the block always breaks, as is common for switch statements, // use the intersection of live-sets at the break points, as computed by // ExitModes. ls = breakSet.vars; } else { ls = ls.intersection(breakSet.vars); } } if (s.isTargetForContinue()) { // Do not intersect the liveset at continue with ls, since continues jump // to the start of the block, and so do not affect the live-set at the end // of the block in the same way that breaks do. em = em.withoutBreakOrContinue(label).withoutBreakOrContinue(""); } else { em = em.withoutBreak(label).withoutBreak(""); } return onExit.withVars(ls).withExits(em); } /** * The set of variables live once a node exits normally, and a description * of the ways in which control may leave a node. */ static final class LiveCalc { final LiveSet vars; final ExitModes exits; private LiveCalc(LiveSet vars, ExitModes exits) { this.vars = vars; this.exits = exits; } private LiveCalc filter(LexicalScope containingScope) { return withVars(vars.filter(containingScope)); } private LiveCalc withExits(ExitModes exits) { return exits == this.exits ? this : new LiveCalc(vars, exits); } private LiveCalc withVars(LiveSet vars) { return vars == this.vars ? this : new LiveCalc(vars, exits); } @Override public String toString() { return "[" + vars + " " + exits + "]"; } } /** * For an expression whose result is used in a boolean computation, the set * of live variables broken down for each of the possible results. * <p> * E.g. in {@code (a = foo()) || (b = bar())} we can confidently say that * when the result is a truthy value */ private static final class ConditionalLiveSet { /** * The set of variables live if the expression produces a value {@code v} * such that {@code !!v === true}. */ final LiveSet truthy; /** * The set of variables live if the expression produces a value {@code v} * such that {@code !!v === false}. */ final LiveSet falsey; ConditionalLiveSet(LiveSet truthy, LiveSet falsey) { this.truthy = truthy; this.falsey = falsey; } ConditionalLiveSet inverse() { return new ConditionalLiveSet(falsey, truthy); } @Override public String toString() { return "(truthy=" + truthy + ", falsey=" + falsey + ")"; } } }