8067774: Use a stack of types when calculating local variable types
Reviewed-by: lagergren, sundar
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Thu Dec 18 12:10:10 2014 +0100
@@ -40,21 +40,22 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.AccessNode;
-import jdk.nashorn.internal.ir.BaseNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.BreakNode;
import jdk.nashorn.internal.ir.BreakableNode;
+import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.CaseNode;
import jdk.nashorn.internal.ir.CatchNode;
import jdk.nashorn.internal.ir.ContinueNode;
import jdk.nashorn.internal.ir.Expression;
+import jdk.nashorn.internal.ir.ExpressionStatement;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
+import jdk.nashorn.internal.ir.GetSplitState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
@@ -65,9 +66,11 @@
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.LoopNode;
import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.ObjectNode;
import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
@@ -82,6 +85,7 @@
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.WhileNode;
+import jdk.nashorn.internal.ir.WithNode;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.TokenType;
@@ -131,8 +135,44 @@
OBJECT(Type.OBJECT);
private final Type type;
+ private final TypeHolderExpression typeExpression;
+
private LvarType(final Type type) {
this.type = type;
+ this.typeExpression = new TypeHolderExpression(type);
+ }
+ }
+
+ /**
+ * A bogus Expression subclass that only reports its type. Used to interrogate BinaryNode and UnaryNode about their
+ * types by creating temporary copies of them and replacing their operands with instances of these. An alternative
+ * solution would be to add BinaryNode.getType(Type lhsType, Type rhsType) and UnaryNode.getType(Type exprType)
+ * methods. For the time being though, this is easier to implement and is in fact fairly clean. It does result in
+ * generation of higher number of temporary short lived nodes, though.
+ */
+ private static class TypeHolderExpression extends Expression {
+ private static final long serialVersionUID = 1L;
+
+ private final Type type;
+
+ TypeHolderExpression(final Type type) {
+ super(0L, 0, 0);
+ this.type = type;
+ }
+
+ @Override
+ public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
+ throw new AssertionError();
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public void toString(final StringBuilder sb, final boolean printType) {
+ throw new AssertionError();
}
}
@@ -359,6 +399,8 @@
// allocates a new map. Immutability of maps allows for cheap snapshots by just keeping the reference to the current
// value.
private Map<Symbol, LvarType> localVariableTypes = new IdentityHashMap<>();
+ // Stack for evaluated expression types.
+ private final Deque<LvarType> typeStack = new ArrayDeque<>();
// Whether the current point in the AST is reachable code
private boolean reachable = true;
@@ -375,8 +417,6 @@
private final Map<IdentNode, LvarType> identifierLvarTypes = new IdentityHashMap<>();
private final Map<Symbol, SymbolConversions> symbolConversions = new IdentityHashMap<>();
- private SymbolToType symbolToType = new SymbolToType();
-
// Stack of open labels for starts of catch blocks, one for every currently traversed try block; for inserting
// control flow edges to them. Note that we currently don't insert actual control flow edges, but instead edges that
// help us with type calculations. This means that some operations that can result in an exception being thrown
@@ -400,62 +440,56 @@
private void doesNotContinueSequentially() {
reachable = false;
localVariableTypes = Collections.emptyMap();
+ assertTypeStackIsEmpty();
}
+ private boolean pushExpressionType(final Expression expr) {
+ typeStack.push(toLvarType(expr.getType()));
+ return false;
+ }
+
+ @Override
+ public boolean enterAccessNode(final AccessNode accessNode) {
+ visitExpression(accessNode.getBase());
+ return pushExpressionType(accessNode);
+ }
@Override
public boolean enterBinaryNode(final BinaryNode binaryNode) {
// NOTE: regardless of operator's lexical associativity, lhs is always evaluated first.
final Expression lhs = binaryNode.lhs();
- final boolean isAssignment = binaryNode.isAssignment();
- LvarType lhsTypeOnLoad = null;
- if(isAssignment) {
- if(lhs instanceof BaseNode) {
- ((BaseNode)lhs).getBase().accept(this);
- if(lhs instanceof IndexNode) {
- ((IndexNode)lhs).getIndex().accept(this);
- } else {
- assert lhs instanceof AccessNode;
- }
- } else {
- assert lhs instanceof IdentNode;
- if(binaryNode.isSelfModifying()) {
- final IdentNode ident = ((IdentNode)lhs);
- ident.accept(this);
- // Self-assignment can cause a change in the type of the variable. For purposes of evaluating
- // the type of the operation, we must use its type as it was when it was loaded. If we didn't
- // do this, some awkward expressions would end up being calculated incorrectly, e.g.
- // "var x; x += x = 0;". In this case we have undefined+int so the result type is double (NaN).
- // However, if we used the type of "x" on LHS after we evaluated RHS, we'd see int+int, so the
- // result type would be either optimistic int or pessimistic long, which would be wrong.
- lhsTypeOnLoad = getLocalVariableTypeIfBytecode(ident.getSymbol());
- }
- }
+ final LvarType lhsType;
+ if (!(lhs instanceof IdentNode && binaryNode.tokenType() == TokenType.ASSIGN)) {
+ lhsType = visitExpression(lhs);
} else {
- lhs.accept(this);
+ // Can't visit IdentNode on LHS of a simple assignment, as visits imply use, and this is def.
+ // The type is irrelevant, as only RHS is used to determine the type anyway.
+ lhsType = LvarType.UNDEFINED;
}
final boolean isLogical = binaryNode.isLogical();
- assert !(isAssignment && isLogical); // there are no logical assignment operators in JS
final Label joinLabel = isLogical ? new Label("") : null;
if(isLogical) {
jumpToLabel((JoinPredecessor)lhs, joinLabel);
}
final Expression rhs = binaryNode.rhs();
- rhs.accept(this);
+ final LvarType rhsType = visitExpression(rhs);
if(isLogical) {
jumpToLabel((JoinPredecessor)rhs, joinLabel);
}
joinOnLabel(joinLabel);
- if(isAssignment && lhs instanceof IdentNode) {
+ final LvarType type = toLvarType(binaryNode.setOperands(lhsType.typeExpression, rhsType.typeExpression).getType());
+
+ if(binaryNode.isAssignment() && lhs instanceof IdentNode) {
if(binaryNode.isSelfModifying()) {
- onSelfAssignment((IdentNode)lhs, binaryNode, lhsTypeOnLoad);
+ onSelfAssignment((IdentNode)lhs, type);
} else {
- onAssignment((IdentNode)lhs, rhs);
+ onAssignment((IdentNode)lhs, type);
}
}
+ typeStack.push(type);
return false;
}
@@ -475,6 +509,17 @@
}
@Override
+ public boolean enterCallNode(final CallNode callNode) {
+ visitExpression(callNode.getFunction());
+ visitExpressions(callNode.getArgs());
+ final CallNode.EvalArgs evalArgs = callNode.getEvalArgs();
+ if (evalArgs != null) {
+ visitExpressions(evalArgs.getArgs());
+ }
+ return pushExpressionType(callNode);
+ }
+
+ @Override
public boolean enterContinueNode(final ContinueNode continueNode) {
return enterJumpStatement(continueNode);
}
@@ -483,6 +528,7 @@
if(!reachable) {
return false;
}
+ assertTypeStackIsEmpty();
final BreakableNode target = jump.getTarget(lc);
jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target));
doesNotContinueSequentially();
@@ -495,6 +541,7 @@
}
private void enterDoWhileLoop(final WhileNode loopNode) {
+ assertTypeStackIsEmpty();
final JoinPredecessorExpression test = loopNode.getTest();
final Block body = loopNode.getBody();
final Label continueLabel = loopNode.getContinueLabel();
@@ -512,7 +559,7 @@
if(!reachable) {
break;
}
- test.accept(this);
+ visitExpressionOnEmptyStack(test);
jumpToLabel(test, breakLabel);
if(isAlwaysFalse(test)) {
break;
@@ -535,6 +582,45 @@
}
@Override
+ public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
+ if (reachable) {
+ visitExpressionOnEmptyStack(expressionStatement.getExpression());
+ }
+ return false;
+ }
+
+ private void assertTypeStackIsEmpty() {
+ assert typeStack.isEmpty();
+ }
+
+ @Override
+ protected Node leaveDefault(final Node node) {
+ assert !(node instanceof Expression); // All expressions were handled
+ assert !(node instanceof Statement) || typeStack.isEmpty(); // No statements leave with a non-empty stack
+ return node;
+ }
+
+ private LvarType visitExpressionOnEmptyStack(final Expression expr) {
+ assertTypeStackIsEmpty();
+ return visitExpression(expr);
+ }
+
+ private LvarType visitExpression(final Expression expr) {
+ final int stackSize = typeStack.size();
+ expr.accept(this);
+ assert typeStack.size() == stackSize + 1;
+ return typeStack.pop();
+ }
+
+ private void visitExpressions(final List<Expression> exprs) {
+ for(final Expression expr: exprs) {
+ if (expr != null) {
+ visitExpression(expr);
+ }
+ }
+ }
+
+ @Override
public boolean enterForNode(final ForNode forNode) {
if(!reachable) {
return false;
@@ -543,7 +629,7 @@
final Expression init = forNode.getInit();
if(forNode.isForIn()) {
final JoinPredecessorExpression iterable = forNode.getModify();
- iterable.accept(this);
+ visitExpression(iterable);
enterTestFirstLoop(forNode, null, init,
// If we're iterating over property names, and we can discern from the runtime environment
// of the compilation that the object being iterated over must use strings for property
@@ -552,16 +638,18 @@
!compiler.useOptimisticTypes() || (!forNode.isForEach() && compiler.hasStringPropertyIterator(iterable.getExpression())));
} else {
if(init != null) {
- init.accept(this);
+ visitExpressionOnEmptyStack(init);
}
enterTestFirstLoop(forNode, forNode.getModify(), null, false);
}
+ assertTypeStackIsEmpty();
return false;
}
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
if(alreadyEnteredTopLevelFunction) {
+ typeStack.push(LvarType.OBJECT);
return false;
}
int pos = 0;
@@ -603,11 +691,20 @@
}
@Override
+ public boolean enterGetSplitState(final GetSplitState getSplitState) {
+ return pushExpressionType(getSplitState);
+ }
+
+ @Override
public boolean enterIdentNode(final IdentNode identNode) {
final Symbol symbol = identNode.getSymbol();
if(symbol.isBytecodeLocal()) {
symbolIsUsed(symbol);
- setIdentifierLvarType(identNode, getLocalVariableType(symbol));
+ final LvarType type = getLocalVariableType(symbol);
+ setIdentifierLvarType(identNode, type);
+ typeStack.push(type);
+ } else {
+ pushExpressionType(identNode);
}
return false;
}
@@ -622,11 +719,12 @@
final Block pass = ifNode.getPass();
final Block fail = ifNode.getFail();
- test.accept(this);
+ visitExpressionOnEmptyStack(test);
final Map<Symbol, LvarType> afterTestLvarTypes = localVariableTypes;
if(!isAlwaysFalse(test)) {
pass.accept(this);
+ assertTypeStackIsEmpty();
}
final Map<Symbol, LvarType> passLvarTypes = localVariableTypes;
final boolean reachableFromPass = reachable;
@@ -635,6 +733,7 @@
localVariableTypes = afterTestLvarTypes;
if(!isAlwaysTrue(test) && fail != null) {
fail.accept(this);
+ assertTypeStackIsEmpty();
final boolean reachableFromFail = reachable;
reachable |= reachableFromPass;
if(!reachable) {
@@ -667,15 +766,54 @@
}
@Override
- public boolean enterPropertyNode(final PropertyNode propertyNode) {
- // Avoid falsely adding property keys to the control flow graph
- if(propertyNode.getValue() != null) {
- propertyNode.getValue().accept(this);
+ public boolean enterIndexNode(final IndexNode indexNode) {
+ visitExpression(indexNode.getBase());
+ visitExpression(indexNode.getIndex());
+ return pushExpressionType(indexNode);
+ }
+
+ @Override
+ public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) {
+ final Expression expr = joinExpr.getExpression();
+ if (expr != null) {
+ expr.accept(this);
+ } else {
+ typeStack.push(LvarType.UNDEFINED);
}
return false;
}
@Override
+ public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
+ if (literalNode instanceof ArrayLiteralNode) {
+ final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions();
+ if (expressions != null) {
+ visitExpressions(expressions);
+ }
+ }
+ pushExpressionType(literalNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterObjectNode(final ObjectNode objectNode) {
+ for(final PropertyNode propertyNode: objectNode.getElements()) {
+ // Avoid falsely adding property keys to the control flow graph
+ final Expression value = propertyNode.getValue();
+ if (value != null) {
+ visitExpression(value);
+ }
+ }
+ return pushExpressionType(objectNode);
+ }
+
+ @Override
+ public boolean enterPropertyNode(final PropertyNode propertyNode) {
+ // Property nodes are only accessible through object literals, and we handled that case above
+ throw new AssertionError();
+ }
+
+ @Override
public boolean enterReturnNode(final ReturnNode returnNode) {
if(!reachable) {
return false;
@@ -684,9 +822,9 @@
final Expression returnExpr = returnNode.getExpression();
final Type returnExprType;
if(returnExpr != null) {
- returnExpr.accept(this);
- returnExprType = getType(returnExpr);
+ returnExprType = visitExpressionOnEmptyStack(returnExpr).type;
} else {
+ assertTypeStackIsEmpty();
returnExprType = Type.UNDEFINED;
}
returnType = Type.widestReturnType(returnType, returnExprType);
@@ -695,6 +833,12 @@
}
@Override
+ public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
+ visitExpressions(runtimeNode.getArgs());
+ return pushExpressionType(runtimeNode);
+ }
+
+ @Override
public boolean enterSplitReturn(final SplitReturn splitReturn) {
doesNotContinueSequentially();
return false;
@@ -706,8 +850,7 @@
return false;
}
- final Expression expr = switchNode.getExpression();
- expr.accept(this);
+ visitExpressionOnEmptyStack(switchNode.getExpression());
final List<CaseNode> cases = switchNode.getCases();
if(cases.isEmpty()) {
@@ -724,7 +867,7 @@
for(final CaseNode caseNode: cases) {
final Expression test = caseNode.getTest();
if(!isInteger && test != null) {
- test.accept(this);
+ visitExpressionOnEmptyStack(test);
if(!tagUsed) {
symbolIsUsed(switchNode.getTag(), LvarType.OBJECT);
tagUsed = true;
@@ -769,29 +912,42 @@
final Expression trueExpr = ternaryNode.getTrueExpression();
final Expression falseExpr = ternaryNode.getFalseExpression();
- test.accept(this);
+ visitExpression(test);
final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes;
+ final LvarType trueType;
if(!isAlwaysFalse(test)) {
- trueExpr.accept(this);
+ trueType = visitExpression(trueExpr);
+ } else {
+ trueType = null;
}
final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes;
localVariableTypes = testExitLvarTypes;
+ final LvarType falseType;
if(!isAlwaysTrue(test)) {
- falseExpr.accept(this);
+ falseType = visitExpression(falseExpr);
+ } else {
+ falseType = null;
}
final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes;
localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes);
setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes);
setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes);
+
+ typeStack.push(trueType != null ? falseType != null ? widestLvarType(trueType, falseType) : trueType : assertNotNull(falseType));
return false;
}
+ private static <T> T assertNotNull(final T t) {
+ assert t != null;
+ return t;
+ }
+
private void enterTestFirstLoop(final LoopNode loopNode, final JoinPredecessorExpression modify,
final Expression iteratorValues, final boolean iteratorValuesAreObject) {
final JoinPredecessorExpression test = loopNode.getTest();
if(isAlwaysFalse(test)) {
- test.accept(this);
+ visitExpressionOnEmptyStack(test);
return;
}
@@ -804,7 +960,7 @@
jumpToLabel(loopNode, repeatLabel, beforeLoopTypes);
final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes;
if(test != null) {
- test.accept(this);
+ visitExpressionOnEmptyStack(test);
}
if(!isAlwaysTrue(test)) {
jumpToLabel(test, breakLabel);
@@ -827,7 +983,7 @@
break;
}
if(modify != null) {
- modify.accept(this);
+ visitExpressionOnEmptyStack(modify);
jumpToLabel(modify, repeatLabel);
joinOnLabel(repeatLabel);
}
@@ -853,7 +1009,7 @@
return false;
}
- throwNode.getExpression().accept(this);
+ visitExpressionOnEmptyStack(throwNode.getExpression());
jumpToCatchBlock(throwNode);
doesNotContinueSequentially();
return false;
@@ -892,7 +1048,7 @@
onAssignment(exception, LvarType.OBJECT);
final Expression condition = catchNode.getExceptionCondition();
if(condition != null) {
- condition.accept(this);
+ visitExpression(condition);
}
final Map<Symbol, LvarType> afterConditionTypes = localVariableTypes;
final Block catchBody = catchNode.getBody();
@@ -927,14 +1083,11 @@
@Override
public boolean enterUnaryNode(final UnaryNode unaryNode) {
final Expression expr = unaryNode.getExpression();
- expr.accept(this);
-
- if(unaryNode.isSelfModifying()) {
- if(expr instanceof IdentNode) {
- final IdentNode ident = (IdentNode)expr;
- onSelfAssignment(ident, unaryNode, getLocalVariableTypeIfBytecode(ident.getSymbol()));
- }
+ final LvarType unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType());
+ if(unaryNode.isSelfModifying() && expr instanceof IdentNode) {
+ onSelfAssignment((IdentNode)expr, unaryType);
}
+ typeStack.push(unaryType);
return false;
}
@@ -945,8 +1098,7 @@
}
final Expression init = varNode.getInit();
if(init != null) {
- init.accept(this);
- onAssignment(varNode.getName(), init);
+ onAssignment(varNode.getName(), visitExpression(init));
}
return false;
}
@@ -964,6 +1116,15 @@
return false;
}
+ @Override
+ public boolean enterWithNode(final WithNode withNode) {
+ if (reachable) {
+ visitExpression(withNode.getExpression());
+ withNode.getBody().accept(this);
+ }
+ return false;
+ };
+
private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) {
// Remove symbols defined in the the blocks that are being broken out of.
Map<Symbol, LvarType> types = localVariableTypes;
@@ -1002,18 +1163,6 @@
}
/**
- * Gets the type for a local variable if it is a bytecode local, otherwise null. Can be used in circumstances where
- * the type is irrelevant if the symbol is not a bytecode local. Note that for bytecode locals, it delegates to
- * {@link #getLocalVariableType(Symbol)}, so it will still assert that the type for such variable is already
- * defined (that is, not null).
- * @param symbol the symbol representing the variable.
- * @return the current variable type, if it is a bytecode local, otherwise null.
- */
- private LvarType getLocalVariableTypeIfBytecode(final Symbol symbol) {
- return symbol.isBytecodeLocal() ? getLocalVariableType(symbol) : null;
- }
-
- /**
* Gets the type for a variable represented by a symbol, or null if the type is not know. This is the least strict
* of all local variable type getters, and as such its use is discouraged except in initialization scenarios (where
* a just-defined symbol might still be null).
@@ -1154,6 +1303,7 @@
*/
private void leaveBreakable(final BreakableNode breakable) {
joinOnLabel(breakable.getBreakLabel());
+ assertTypeStackIsEmpty();
}
@Override
@@ -1329,10 +1479,6 @@
return conv == null || !conv.isLive();
}
- private void onAssignment(final IdentNode identNode, final Expression rhs) {
- onAssignment(identNode, toLvarType(getType(rhs)));
- }
-
private void onAssignment(final IdentNode identNode, final LvarType type) {
final Symbol symbol = identNode.getSymbol();
assert symbol != null : identNode.getName();
@@ -1400,13 +1546,12 @@
jumpToCatchBlock(identNode);
}
- private void onSelfAssignment(final IdentNode identNode, final Expression assignment, final LvarType typeOnLoad) {
+ private void onSelfAssignment(final IdentNode identNode, final LvarType type) {
final Symbol symbol = identNode.getSymbol();
assert symbol != null : identNode.getName();
if(!symbol.isBytecodeLocal()) {
return;
}
- final LvarType type = toLvarType(getType(assignment, symbol, typeOnLoad.type));
// Self-assignment never produce either a boolean or undefined
assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN;
setType(symbol, type);
@@ -1466,7 +1611,6 @@
* @param symbol the symbol representing the variable
* @param type the type
*/
- @SuppressWarnings("unused")
private void setType(final Symbol symbol, final LvarType type) {
if(getLocalVariableTypeOrNull(symbol) == type) {
return;
@@ -1486,77 +1630,4 @@
private void symbolIsUsed(final Symbol symbol) {
symbolIsUsed(symbol, getLocalVariableType(symbol));
}
-
- /**
- * Gets the type of the expression, dependent on the current types of the local variables.
- *
- * @param expr the expression
- * @return the current type of the expression dependent on the current types of the local variables.
- */
- private Type getType(final Expression expr) {
- return expr.getType(getSymbolToType());
- }
-
- /**
- * Returns a function object from symbols to their types, used by the expressions to evaluate their type.
- * {@link BinaryNode} specifically uses identity of the function to cache type calculations. This method makes
- * sure to return the same function object while the local variable types don't change, and create a new function
- * object if the local variable types have been changed.
- * @return a function object representing a mapping from symbols to their types.
- */
- private Function<Symbol, Type> getSymbolToType() {
- if(symbolToType.isStale()) {
- symbolToType = new SymbolToType();
- }
- return symbolToType;
- }
-
- private class SymbolToType implements Function<Symbol, Type> {
- private final Object boundTypes = localVariableTypes;
- @Override
- public Type apply(final Symbol t) {
- return getLocalVariableType(t).type;
- }
-
- boolean isStale() {
- return boundTypes != localVariableTypes;
- }
- }
-
- /**
- * Gets the type of the expression, dependent on the current types of the local variables and a single overridden
- * symbol type. Used by type calculation on compound operators to ensure the type of the LHS at the time it was
- * loaded (which can potentially be different after RHS evaluation, e.g. "var x; x += x = 0;") is preserved for
- * the calculation.
- *
- * @param expr the expression
- * @param overriddenSymbol the overridden symbol
- * @param overriddenType the overridden type
- * @return the current type of the expression dependent on the current types of the local variables and the single
- * potentially overridden type.
- */
- private Type getType(final Expression expr, final Symbol overriddenSymbol, final Type overriddenType) {
- return expr.getType(getSymbolToType(overriddenSymbol, overriddenType));
- }
-
- private Function<Symbol, Type> getSymbolToType(final Symbol overriddenSymbol, final Type overriddenType) {
- return getLocalVariableType(overriddenSymbol).type == overriddenType ? getSymbolToType() :
- new SymbolToTypeOverride(overriddenSymbol, overriddenType);
- }
-
- private class SymbolToTypeOverride implements Function<Symbol, Type> {
- private final Function<Symbol, Type> originalSymbolToType = getSymbolToType();
- private final Symbol overriddenSymbol;
- private final Type overriddenType;
-
- SymbolToTypeOverride(final Symbol overriddenSymbol, final Type overriddenType) {
- this.overriddenSymbol = overriddenSymbol;
- this.overriddenType = overriddenType;
- }
-
- @Override
- public Type apply(final Symbol symbol) {
- return symbol == overriddenSymbol ? overriddenType : originalSymbolToType.apply(symbol);
- }
- }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BaseNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BaseNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -27,7 +27,6 @@
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
@@ -98,7 +97,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return type == null ? getMostPessimisticType() : type;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BinaryNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BinaryNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -31,7 +31,6 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Ignore;
import jdk.nashorn.internal.ir.annotations.Immutable;
@@ -57,9 +56,7 @@
private final int programPoint;
private final Type type;
-
private transient Type cachedType;
- private transient Object cachedTypeFunction;
@Ignore
private static final Set<TokenType> CAN_OVERFLOW =
@@ -143,24 +140,6 @@
}
}
- private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
- @Override
- public Type apply(final Symbol t) {
- return null;
- }
- };
-
- /**
- * Return the widest possible type for this operation. This is used for compile time
- * static type inference
- *
- * @return Type
- */
- @Override
- public Type getWidestOperationType() {
- return getWidestOperationType(UNKNOWN_LOCALS);
- }
-
/**
* Return the widest possible operand type for this operation.
*
@@ -181,14 +160,15 @@
}
}
- private Type getWidestOperationType(final Function<Symbol, Type> localVariableTypes) {
+ @Override
+ public Type getWidestOperationType() {
switch (tokenType()) {
case ADD:
case ASSIGN_ADD: {
// Compare this logic to decideType(Type, Type); it's similar, but it handles the optimistic type
// calculation case while this handles the conservative case.
- final Type lhsType = lhs.getType(localVariableTypes);
- final Type rhsType = rhs.getType(localVariableTypes);
+ final Type lhsType = lhs.getType();
+ final Type rhsType = rhs.getType();
if(lhsType == Type.BOOLEAN && rhsType == Type.BOOLEAN) {
// Will always fit in an int, as the value range is [0, 1, 2]. If we didn't treat them specially here,
// they'd end up being treated as generic INT operands and their sum would be conservatively considered
@@ -238,8 +218,8 @@
case SUB:
case ASSIGN_MUL:
case ASSIGN_SUB: {
- final Type lhsType = lhs.getType(localVariableTypes);
- final Type rhsType = rhs.getType(localVariableTypes);
+ final Type lhsType = lhs.getType();
+ final Type rhsType = rhs.getType();
if(lhsType == Type.BOOLEAN && rhsType == Type.BOOLEAN) {
return Type.INT;
}
@@ -253,20 +233,20 @@
return Type.UNDEFINED;
}
case ASSIGN: {
- return rhs.getType(localVariableTypes);
+ return rhs.getType();
}
case INSTANCEOF: {
return Type.BOOLEAN;
}
case COMMALEFT: {
- return lhs.getType(localVariableTypes);
+ return lhs.getType();
}
case COMMARIGHT: {
- return rhs.getType(localVariableTypes);
+ return rhs.getType();
}
case AND:
case OR:{
- return Type.widestReturnType(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes));
+ return Type.widestReturnType(lhs.getType(), rhs.getType());
}
default:
if (isComparison()) {
@@ -487,7 +467,7 @@
/**
* Set the right hand side expression for this node
- * @param rhs new left hand side expression
+ * @param rhs new right hand side expression
* @return a node equivalent to this one except for the requested change.
*/
public BinaryNode setRHS(final Expression rhs) {
@@ -497,6 +477,19 @@
return new BinaryNode(this, lhs, rhs, type, programPoint);
}
+ /**
+ * Set both the left and the right hand side expression for this node
+ * @param lhs new left hand side expression
+ * @param rhs new left hand side expression
+ * @return a node equivalent to this one except for the requested change.
+ */
+ public BinaryNode setOperands(final Expression lhs, final Expression rhs) {
+ if (this.lhs == lhs && this.rhs == rhs) {
+ return this;
+ }
+ return new BinaryNode(this, lhs, rhs, type, programPoint);
+ }
+
@Override
public int getProgramPoint() {
return programPoint;
@@ -541,24 +534,22 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
- if(localVariableTypes == cachedTypeFunction) {
- return cachedType;
+ public Type getType() {
+ if (cachedType == null) {
+ cachedType = getTypeUncached();
}
- cachedType = getTypeUncached(localVariableTypes);
- cachedTypeFunction = localVariableTypes;
return cachedType;
}
- private Type getTypeUncached(final Function<Symbol, Type> localVariableTypes) {
+ private Type getTypeUncached() {
if(type == OPTIMISTIC_UNDECIDED_TYPE) {
- return decideType(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes));
+ return decideType(lhs.getType(), rhs.getType());
}
- final Type widest = getWidestOperationType(localVariableTypes);
+ final Type widest = getWidestOperationType();
if(type == null) {
return widest;
}
- return Type.narrowest(widest, Type.widest(type, Type.widest(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes))));
+ return Type.narrowest(widest, Type.widest(type, Type.widest(lhs.getType(), rhs.getType())));
}
private static Type decideType(final Type lhsType, final Type rhsType) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/CallNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/CallNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -30,7 +30,6 @@
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Ignore;
import jdk.nashorn.internal.ir.annotations.Immutable;
@@ -154,7 +153,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return optimisticType == null ? Type.OBJECT : optimisticType;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Expression.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Expression.java Thu Dec 18 12:10:10 2014 +0100
@@ -25,7 +25,6 @@
package jdk.nashorn.internal.ir;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
@@ -39,14 +38,7 @@
static final String OPT_IDENTIFIER = "%";
- private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
- @Override
- public Type apply(final Symbol t) {
- return null;
- }
- };
-
- Expression(final long token, final int start, final int finish) {
+ protected Expression(final long token, final int start, final int finish) {
super(token, start, finish);
}
@@ -63,18 +55,7 @@
*
* @return the type of the expression.
*/
- public final Type getType() {
- return getType(UNKNOWN_LOCALS);
- }
-
- /**
- * Returns the type of the expression under the specified symbol-to-type mapping. By default delegates to
- * {@link #getType()} but expressions whose type depends on their subexpressions' types and expressions whose type
- * depends on symbol type ({@link IdentNode}) will have a special implementation.
- * @param localVariableTypes a mapping from symbols to their types, used for type calculation.
- * @return the type of the expression under the specified symbol-to-type mapping.
- */
- public abstract Type getType(final Function<Symbol, Type> localVariableTypes);
+ public abstract Type getType();
/**
* Returns {@code true} if this expression depends exclusively on state that is constant
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -36,7 +36,6 @@
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.AssertsEnabled;
import jdk.nashorn.internal.codegen.CompileUnit;
import jdk.nashorn.internal.codegen.Compiler;
@@ -1101,7 +1100,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return FUNCTION_TYPE;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/GetSplitState.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/GetSplitState.java Thu Dec 18 12:10:10 2014 +0100
@@ -25,7 +25,6 @@
package jdk.nashorn.internal.ir;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -47,7 +46,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.INT;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/IdentNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/IdentNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -30,7 +30,6 @@
import static jdk.nashorn.internal.codegen.CompilerConstants.__LINE__;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -118,14 +117,13 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
if(type != null) {
return type;
} else if(symbol != null && symbol.isScope()) {
return Type.OBJECT;
}
- final Type symbolType = localVariableTypes.apply(symbol);
- return symbolType == null ? Type.UNDEFINED : symbolType;
+ return Type.UNDEFINED;
}
/**
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JoinPredecessorExpression.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/JoinPredecessorExpression.java Thu Dec 18 12:10:10 2014 +0100
@@ -25,7 +25,6 @@
package jdk.nashorn.internal.ir;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -71,8 +70,8 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
- return expression.getType(localVariableTypes);
+ public Type getType() {
+ return expression.getType();
}
@Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LiteralNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LiteralNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -29,7 +29,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.CompileUnit;
import jdk.nashorn.internal.codegen.types.ArrayType;
import jdk.nashorn.internal.codegen.types.Type;
@@ -109,7 +108,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.typeFor(value.getClass());
}
@@ -164,16 +163,6 @@
}
/**
- * Get the array value of the node
- *
- * @return the array value
- */
- public Node[] getArray() {
- assert false : "not an array node";
- return null;
- }
-
- /**
* Fetch String value of node.
*
* @return String value of node.
@@ -325,7 +314,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.BOOLEAN;
}
@@ -389,7 +378,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return type;
}
@@ -519,7 +508,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.OBJECT;
}
@@ -589,7 +578,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.OBJECT;
}
@@ -840,9 +829,13 @@
this.units = units;
}
- @Override
- public Node[] getArray() {
- return value;
+ /**
+ * Returns a list of array element expressions. Note that empty array elements manifest themselves as
+ * null.
+ * @return a list of array element expressions.
+ */
+ public List<Expression> getElementExpressions() {
+ return Collections.unmodifiableList(Arrays.asList(value));
}
/**
@@ -879,7 +872,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.typeFor(NativeArray.class);
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ObjectNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ObjectNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -27,7 +27,6 @@
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -69,7 +68,7 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return Type.OBJECT;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -30,7 +30,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -460,7 +459,7 @@
* Return type for the ReferenceNode
*/
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ public Type getType() {
return request.getReturnType();
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TernaryNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/TernaryNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -25,7 +25,6 @@
package jdk.nashorn.internal.ir;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -122,8 +121,8 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
- return Type.widestReturnType(getTrueExpression().getType(localVariableTypes), getFalseExpression().getType(localVariableTypes));
+ public Type getType() {
+ return Type.widestReturnType(getTrueExpression().getType(), getFalseExpression().getType());
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/UnaryNode.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/UnaryNode.java Thu Dec 18 12:10:10 2014 +0100
@@ -33,7 +33,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Ignore;
import jdk.nashorn.internal.ir.annotations.Immutable;
@@ -123,23 +122,11 @@
return isAssignment();
}
- private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
- @Override
- public Type apply(final Symbol t) {
- return null;
- }
- };
-
-
@Override
public Type getWidestOperationType() {
- return getWidestOperationType(UNKNOWN_LOCALS);
- }
-
- private Type getWidestOperationType(final Function<Symbol, Type> localVariableTypes) {
switch (tokenType()) {
case ADD:
- final Type operandType = getExpression().getType(localVariableTypes);
+ final Type operandType = getExpression().getType();
if(operandType == Type.BOOLEAN) {
return Type.INT;
} else if(operandType.isObject()) {
@@ -326,12 +313,12 @@
}
@Override
- public Type getType(final Function<Symbol, Type> localVariableTypes) {
- final Type widest = getWidestOperationType(localVariableTypes);
+ public Type getType() {
+ final Type widest = getWidestOperationType();
if(type == null) {
return widest;
}
- return Type.narrowest(widest, Type.widest(type, expression.getType(localVariableTypes)));
+ return Type.narrowest(widest, Type.widest(type, expression.getType()));
}
@Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/JSONWriter.java Thu Dec 18 16:33:33 2014 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/debug/JSONWriter.java Thu Dec 18 12:10:10 2014 +0100
@@ -28,7 +28,6 @@
import static jdk.nashorn.internal.runtime.Source.sourceFor;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BinaryNode;
@@ -553,8 +552,7 @@
type("ArrayExpression");
comma();
- final Node[] value = literalNode.getArray();
- array("elements", Arrays.asList(value));
+ array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions());
} else {
type("Literal");
comma();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8067774.js Thu Dec 18 12:10:10 2014 +0100
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8067774: Use a stack of types when calculating local variable types
+ *
+ * @test
+ * @run
+ */
+
+print((function (p) {
+ var a, b;
+
+ a = p ? ((b = 1), b) : 0;
+
+ return a;
+})(true));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8067774.js.EXPECTED Thu Dec 18 12:10:10 2014 +0100
@@ -0,0 +1,1 @@
+1