This commit is contained in:
Lana Steuck 2014-12-11 12:26:56 -08:00
commit 326c95a872
41 changed files with 1044 additions and 125 deletions

View File

@ -43,7 +43,7 @@
classpath="${javac.classpath}"
debug="${javac.debug}"
includeantruntime="false" fork="true">
<compilerarg value="-J-Djava.ext.dirs="/>
<compilerarg value="-J-Xbootclasspath/p:${javac.classpath}"/>
<compilerarg value="-Xlint:unchecked"/>
<compilerarg value="-Xlint:deprecation"/>
<compilerarg value="-XDignore.symbol.file"/>

View File

@ -39,7 +39,7 @@
<pathelement location="${dist.dir}/nasgen.jar"/>
<pathelement path="${build.dir}/classes"/>
</classpath>
<jvmarg value="-Djava.ext.dirs="/>
<jvmarg value="${boot.class.path}"/>
<arg value="${build.dir}/classes"/>
<arg value="jdk.nashorn.internal.objects"/>
<arg value="${build.dir}/classes"/>

View File

@ -38,7 +38,7 @@
<path id="nashorn.boot.prefix.path">
<pathelement location="${dist.jar}"/>
</path>
<property name="boot.class.path" value="-Xbootclasspath/p:&quot;${toString:nashorn.boot.prefix.path}&quot;"/>
<property name="boot.class.path" value="-Xbootclasspath/p:${toString:nashorn.boot.prefix.path}"/>
<condition property="svn.executable" value="/usr/local/bin/svn" else="svn">
<available file="/usr/local/bin/svn"/>
</condition>
@ -271,6 +271,7 @@
debug="${javac.debug}"
encoding="${javac.encoding}"
includeantruntime="false" fork="true">
<compilerarg value="${boot.class.path}"/>
<compilerarg value="-Xlint:unchecked"/>
<compilerarg value="-Xlint:deprecation"/>
<compilerarg value="-Xdiags:verbose"/>

View File

@ -284,7 +284,7 @@ run.test.jvmargs.common=\
-XX:+HeapDumpOnOutOfMemoryError
# turn on assertions for tests
run.test.jvmargs.main=${run.test.jvmargs.common} -ea
run.test.jvmargs.main=${run.test.jvmargs.common} -esa -ea
# Extra jvmargs that might be useful for debugging
# and performance improvements/monitoring

View File

@ -0,0 +1,91 @@
#// Usage: jjs -fx browser.js
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
if (!$OPTIONS._fx) {
print("Usage: jjs -fx browser.js");
exit(1);
}
// JavaFX classes used
var ChangeListener = Java.type("javafx.beans.value.ChangeListener");
var Scene = Java.type("javafx.scene.Scene");
var WebView = Java.type("javafx.scene.web.WebView");
var EventListener = Java.type("org.w3c.dom.events.EventListener");
// JavaFX start method
function start(stage) {
start.title = "Web View";
var wv = new WebView();
wv.engine.loadContent(<<EOF
<html>
<head>
<title>
This is the title
</title>
<script>
// click count for OK button
var okCount = 0;
</script>
</head>
<body>
Button from the input html<br>
<button type="button" onclick="okCount++">OK</button><br>
</body>
</html>
EOF, "text/html");
// attach onload handler
wv.engine.loadWorker.stateProperty().addListener(
new ChangeListener() {
changed: function() {
// DOM document element
var document = wv.engine.document;
// DOM manipulation
var btn = document.createElement("button");
var n = 0;
// attach a button handler - nashorn function!
btn.onclick = new EventListener(function() {
n++; print("You clicked " + n + " time(s)");
print("you clicked OK " + wv.engine.executeScript("okCount"));
});
// attach text to button
var t = document.createTextNode("Click Me!");
btn.appendChild(t);
// attach button to the document
document.body.appendChild(btn);
}
}
);
stage.scene = new Scene(wv, 750, 500);
stage.show();
}

View File

@ -917,7 +917,7 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
@Override
public Node leaveSwitchNode(final SwitchNode switchNode) {
// We only need a symbol for the tag if it's not an integer switch node
if(!switchNode.isInteger()) {
if(!switchNode.isUniqueInteger()) {
switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX));
}
return switchNode;

View File

@ -465,10 +465,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
// If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert
// it anyway for replaceLocationPropertyPlaceholder.
if(identNode.isCompileTimePropertyName()) {
method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction());
method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction(), false);
replaceCompileTimeProperty();
} else {
dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction());
dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction(), false);
}
}
}
@ -486,7 +486,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) {
loadFastScopeProto(symbol, true);
method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE);
method.dynamicSet(symbol.getName(), flags | CALLSITE_FAST_SCOPE, false);
return method;
}
@ -571,9 +571,11 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
// Operands' load type should not be narrower than the narrowest of the individual operand types, nor narrower
// than the lower explicit bound, but it should also not be wider than
final Type narrowestOperandType = Type.narrowest(Type.widest(lhs.getType(), rhs.getType()), explicitOperandBounds.widest);
final Type lhsType = undefinedToNumber(lhs.getType());
final Type rhsType = undefinedToNumber(rhs.getType());
final Type narrowestOperandType = Type.narrowest(Type.widest(lhsType, rhsType), explicitOperandBounds.widest);
final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType);
if (noToPrimitiveConversion(lhs.getType(), explicitOperandBounds.widest) || rhs.isLocal()) {
if (noToPrimitiveConversion(lhsType, explicitOperandBounds.widest) || rhs.isLocal()) {
// Can reorder. We might still need to separate conversion, but at least we can do it with reordering
if (forceConversionSeparation) {
// Can reorder, but can't move conversion into the operand as the operation depends on operands
@ -594,10 +596,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
// Can't reorder. Load and convert separately.
final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType);
loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack);
final Type lhsType = method.peekType();
final Type lhsLoadedType = method.peekType();
loadExpression(rhs, safeConvertBounds, false);
final Type convertedLhsType = operandBounds.within(method.peekType());
if (convertedLhsType != lhsType) {
if (convertedLhsType != lhsLoadedType) {
// Do it conditionally, so that if conversion is a no-op we don't introduce a SWAP, SWAP.
method.swap().convert(convertedLhsType).swap();
}
@ -609,6 +611,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return method;
}
private static final Type undefinedToNumber(final Type type) {
return type == Type.UNDEFINED ? Type.NUMBER : type;
}
private static final class TypeBounds {
final Type narrowest;
final Type widest;
@ -739,7 +745,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
@Override
void consumeStack() {
final int flags = getCallSiteFlags();
dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction());
dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction(), accessNode.isIndex());
}
}.emit(baseAlreadyOnStack ? 1 : 0);
return false;
@ -1443,7 +1449,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
// NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back
// a callable object. Nobody in their right mind would optimistically type this call site.
assert !node.isOptimistic();
method.dynamicGet(node.getType(), node.getProperty(), flags, true);
method.dynamicGet(node.getType(), node.getProperty(), flags, true, node.isIndex());
method.swap();
argCount = loadArgs(args);
}
@ -2015,6 +2021,19 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
final Expression test = ifNode.getTest();
final Block pass = ifNode.getPass();
final Block fail = ifNode.getFail();
if (Expression.isAlwaysTrue(test)) {
loadAndDiscard(test);
pass.accept(this);
return false;
} else if (Expression.isAlwaysFalse(test)) {
loadAndDiscard(test);
if (fail != null) {
fail.accept(this);
}
return false;
}
final boolean hasFailConversion = LocalVariableConversion.hasLiveConversion(ifNode);
final Label failLabel = new Label("if_fail");
@ -2034,7 +2053,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.beforeJoinPoint(ifNode);
}
if(afterLabel != null) {
if(afterLabel != null && afterLabel.isReachable()) {
method.label(afterLabel);
}
@ -2811,7 +2830,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
Label defaultLabel = defaultCase != null ? defaultCase.getEntry() : breakLabel;
final boolean hasSkipConversion = LocalVariableConversion.hasLiveConversion(switchNode);
if (switchNode.isInteger()) {
if (switchNode.isUniqueInteger()) {
// Tree for sorting values.
final TreeMap<Integer, Label> tree = new TreeMap<>();
@ -3146,14 +3165,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
if (isFastScope(identSymbol)) {
storeFastScopeVar(identSymbol, flags);
} else {
method.dynamicSet(identNode.getName(), flags);
method.dynamicSet(identNode.getName(), flags, false);
}
} else {
final Type identType = identNode.getType();
if(identType == Type.UNDEFINED) {
// The symbol must not be slotted; the initializer is either itself undefined (explicit assignment of
// undefined to undefined), or the left hand side is a dead variable.
assert !identNode.getSymbol().isScope();
// The initializer is either itself undefined (explicit assignment of undefined to undefined),
// or the left hand side is a dead variable.
assert init.getType() == Type.UNDEFINED || identNode.getSymbol().slotCount() == 0;
loadAndDiscard(init);
return false;
@ -3265,7 +3283,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
emitContinueLabel(continueLabel, liveLocalsOnContinue);
}
if (loopNode.hasPerIterationScope() && lc.getParentBlock().needsScope()) {
if (loopNode.hasPerIterationScope() && lc.getCurrentBlock().needsScope()) {
// ES6 for loops with LET init need a new scope for each iteration. We just create a shallow copy here.
method.loadCompilerConstant(SCOPE);
method.invoke(virtualCallNoLookup(ScriptObject.class, "copy", ScriptObject.class));
@ -3576,9 +3594,9 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
operandBounds = new TypeBounds(binaryNode.getType(), Type.OBJECT);
} else {
// Non-optimistic, non-FP +. Allow it to overflow.
operandBounds = new TypeBounds(Type.narrowest(binaryNode.getWidestOperandType(), resultBounds.widest),
Type.OBJECT);
forceConversionSeparation = binaryNode.getWidestOperationType().narrowerThan(resultBounds.widest);
final Type widestOperationType = binaryNode.getWidestOperationType();
operandBounds = new TypeBounds(Type.narrowest(binaryNode.getWidestOperandType(), resultBounds.widest), widestOperationType);
forceConversionSeparation = widestOperationType.narrowerThan(resultBounds.widest);
}
loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false, forceConversionSeparation);
}
@ -3693,8 +3711,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
final Expression lhs = assignNode.lhs();
final Expression rhs = assignNode.rhs();
final Type widestOperationType = assignNode.getWidestOperationType();
final Type widest = assignNode.isTokenType(TokenType.ASSIGN_ADD) ? Type.OBJECT : widestOperationType;
final TypeBounds bounds = new TypeBounds(assignNode.getType(), widest);
final TypeBounds bounds = new TypeBounds(assignNode.getType(), widestOperationType);
new OptimisticOperation(assignNode, bounds) {
@Override
void loadStack() {
@ -4252,7 +4269,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
if (isFastScope(symbol)) {
storeFastScopeVar(symbol, flags);
} else {
method.dynamicSet(node.getName(), flags);
method.dynamicSet(node.getName(), flags, false);
}
} else {
final Type storeType = assignNode.getType();
@ -4269,7 +4286,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
@Override
public boolean enterAccessNode(final AccessNode node) {
method.dynamicSet(node.getProperty(), getCallSiteFlags());
method.dynamicSet(node.getProperty(), getCallSiteFlags(), node.isIndex());
return false;
}
@ -4607,11 +4624,11 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
* @param isMethod whether we're preferrably retrieving a function
* @return the current method emitter
*/
MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod) {
MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod, final boolean isIndex) {
if(isOptimistic) {
return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod);
return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod, isIndex);
}
return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod);
return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod, isIndex);
}
MethodEmitter dynamicGetIndex(final int flags, final boolean isMethod) {

View File

@ -26,12 +26,16 @@
package jdk.nashorn.internal.codegen;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.BlockStatement;
import jdk.nashorn.internal.ir.CaseNode;
import jdk.nashorn.internal.ir.EmptyNode;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.IfNode;
@ -40,6 +44,7 @@ import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.TernaryNode;
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
@ -126,11 +131,37 @@ final class FoldConstants extends NodeVisitor<LexicalContext> implements Loggabl
public Node leaveTernaryNode(final TernaryNode ternaryNode) {
final Node test = ternaryNode.getTest();
if (test instanceof LiteralNode.PrimitiveLiteralNode) {
return ((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue() ? ternaryNode.getTrueExpression() : ternaryNode.getFalseExpression();
return (((LiteralNode.PrimitiveLiteralNode<?>)test).isTrue() ? ternaryNode.getTrueExpression() : ternaryNode.getFalseExpression()).getExpression();
}
return ternaryNode;
}
@Override
public Node leaveSwitchNode(final SwitchNode switchNode) {
return switchNode.setUniqueInteger(lc, isUniqueIntegerSwitchNode(switchNode));
}
private static boolean isUniqueIntegerSwitchNode(final SwitchNode switchNode) {
final Set<Integer> alreadySeen = new HashSet<>();
for (final CaseNode caseNode : switchNode.getCases()) {
final Expression test = caseNode.getTest();
if (test != null && !isUniqueIntegerLiteral(test, alreadySeen)) {
return false;
}
}
return true;
}
private static boolean isUniqueIntegerLiteral(final Expression expr, final Set<Integer> alreadySeen) {
if (expr instanceof LiteralNode) {
final Object value = ((LiteralNode<?>)expr).getValue();
if (value instanceof Integer) {
return alreadySeen.add((Integer)value);
}
}
return false;
}
/**
* Helper class to evaluate constant expressions at compile time This is
* also a simplifier used by BinaryNode visits, UnaryNode visits and

View File

@ -28,6 +28,7 @@ package jdk.nashorn.internal.codegen;
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.ir.Expression.isAlwaysFalse;
import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@ -82,7 +83,6 @@ import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.WhileNode;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;
/**
@ -93,6 +93,13 @@ import jdk.nashorn.internal.parser.TokenType;
* variable to its widest used type after the join point. That would eliminate some widenings of undefined variables to
* object, most notably those used only in loops. We need a full liveness analysis for that. Currently, we can establish
* per-type liveness, which eliminates most of unwanted dead widenings.
* NOTE: the way this class is implemented, it actually processes the AST in two passes. The first pass is top-down and
* implemented in {@code enterXxx} methods. This pass does not mutate the AST (except for one occurrence, noted below),
* as being able to find relevant labels for control flow joins is sensitive to their reference identity, and mutated
* label-carrying nodes will create copies of their labels. A second bottom-up pass applying the changes is implemented
* in the separate visitor sitting in {@link #leaveFunctionNode(FunctionNode)}. This visitor will also instantiate new
* instances of the calculator to be run on nested functions (when not lazy compiling).
*
*/
final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
@ -398,48 +405,53 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
@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 Expression rhs = binaryNode.rhs();
final boolean isAssignment = binaryNode.isAssignment();
final TokenType tokenType = Token.descType(binaryNode.getToken());
if(tokenType.isLeftAssociative()) {
assert !isAssignment;
final boolean isLogical = binaryNode.isLogical();
final Label joinLabel = isLogical ? new Label("") : null;
lhs.accept(this);
if(isLogical) {
jumpToLabel((JoinPredecessor)lhs, joinLabel);
}
rhs.accept(this);
if(isLogical) {
jumpToLabel((JoinPredecessor)rhs, joinLabel);
}
joinOnLabel(joinLabel);
} else {
rhs.accept(this);
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;
}
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 IdentNode;
if(binaryNode.isSelfModifying()) {
((IdentNode)lhs).accept(this);
}
assert lhs instanceof AccessNode;
}
} else {
lhs.accept(this);
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());
}
}
} else {
lhs.accept(this);
}
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);
if(isLogical) {
jumpToLabel((JoinPredecessor)rhs, joinLabel);
}
joinOnLabel(joinLabel);
if(isAssignment && lhs instanceof IdentNode) {
if(binaryNode.isSelfModifying()) {
onSelfAssignment((IdentNode)lhs, binaryNode);
onSelfAssignment((IdentNode)lhs, binaryNode, lhsTypeOnLoad);
} else {
onAssignment((IdentNode)lhs, rhs);
}
@ -704,7 +716,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
// Control flow is different for all-integer cases where we dispatch by switch table, and for all other cases
// where we do sequential comparison. Note that CaseNode objects act as join points.
final boolean isInteger = switchNode.isInteger();
final boolean isInteger = switchNode.isUniqueInteger();
final Label breakLabel = switchNode.getBreakLabel();
final boolean hasDefault = switchNode.getDefaultCase() != null;
@ -919,7 +931,8 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
if(unaryNode.isSelfModifying()) {
if(expr instanceof IdentNode) {
onSelfAssignment((IdentNode)expr, unaryNode);
final IdentNode ident = (IdentNode)expr;
onSelfAssignment(ident, unaryNode, getLocalVariableTypeIfBytecode(ident.getSymbol()));
}
}
return false;
@ -973,12 +986,41 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
return types;
}
/**
* Returns the current type of the local variable represented by the symbol. This is the most strict of all
* {@code getLocalVariableType*} methods, as it will throw an assertion if the type is null. Therefore, it is only
* safe to be invoked on symbols known to be bytecode locals, and only after they have been initialized.
* Regardless, it is recommended to use this method in majority of cases, as because of its strictness it is the
* best suited for catching missing type calculation bugs early.
* @param symbol a symbol representing a bytecode local variable.
* @return the current type of the local variable represented by the symbol
*/
private LvarType getLocalVariableType(final Symbol symbol) {
final LvarType type = getLocalVariableTypeOrNull(symbol);
assert type != null;
return type;
}
/**
* 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).
* @param symbol the symbol
* @return the current type for the symbol, or null if the type is not known either because the symbol has not been
* initialized, or because the symbol does not represent a bytecode local variable.
*/
private LvarType getLocalVariableTypeOrNull(final Symbol symbol) {
return localVariableTypes.get(symbol);
}
@ -1358,13 +1400,13 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
jumpToCatchBlock(identNode);
}
private void onSelfAssignment(final IdentNode identNode, final Expression assignment) {
private void onSelfAssignment(final IdentNode identNode, final Expression assignment, final LvarType typeOnLoad) {
final Symbol symbol = identNode.getSymbol();
assert symbol != null : identNode.getName();
if(!symbol.isBytecodeLocal()) {
return;
}
final LvarType type = toLvarType(getType(assignment));
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);
@ -1445,13 +1487,24 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
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() {
// BinaryNode uses identity of the function to cache type calculations. Therefore, we must use different
// function instances for different localVariableTypes instances.
if(symbolToType.isStale()) {
symbolToType = new SymbolToType();
}
@ -1469,4 +1522,41 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
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);
}
}
}

View File

@ -34,6 +34,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Pattern;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BaseNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
@ -52,6 +54,7 @@ import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.JumpStatement;
import jdk.nashorn.internal.ir.LabelNode;
import jdk.nashorn.internal.ir.LexicalContext;
@ -93,6 +96,10 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
private final DebugLogger log;
// Conservative pattern to test if element names consist of characters valid for identifiers.
// This matches any non-zero length alphanumeric string including _ and $ and not starting with a digit.
private static Pattern SAFE_PROPERTY_NAME = Pattern.compile("[a-zA-Z_$][\\w$]*");
/**
* Constructor.
*/
@ -140,7 +147,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
}
});
this.log = initLogger(compiler.getContext());
this.log = initLogger(compiler.getContext());
}
@Override
@ -180,6 +187,28 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
return false;
}
@Override
public Node leaveIndexNode(final IndexNode indexNode) {
final String name = getConstantPropertyName(indexNode.getIndex());
if (name != null) {
// If index node is a constant property name convert index node to access node.
assert Token.descType(indexNode.getToken()) == TokenType.LBRACKET;
return new AccessNode(indexNode.getToken(), indexNode.getFinish(), indexNode.getBase(), name);
}
return super.leaveIndexNode(indexNode);
}
// If expression is a primitive literal that is not an array index and does return its string value. Else return null.
private static String getConstantPropertyName(final Expression expression) {
if (expression instanceof LiteralNode.PrimitiveLiteralNode) {
final Object value = ((LiteralNode) expression).getValue();
if (value instanceof String && SAFE_PROPERTY_NAME.matcher((String) value).matches()) {
return (String) value;
}
}
return null;
}
@Override
public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
final Expression expr = expressionStatement.getExpression();
@ -275,7 +304,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
@Override
public Node leaveSwitchNode(final SwitchNode switchNode) {
if(!switchNode.isInteger()) {
if(!switchNode.isUniqueInteger()) {
// Wrap it in a block so its internally created tag is restricted in scope
addStatementEnclosedInBlock(switchNode);
} else {

View File

@ -2213,10 +2213,10 @@ public class MethodEmitter implements Emitter {
* @param name name of property
* @param flags call site flags
* @param isMethod should it prefer retrieving methods
*
* @param isIndex is this an index operation?
* @return the method emitter
*/
MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) {
MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod, final boolean isIndex) {
if (name.length() > LARGE_STRING_THRESHOLD) { // use getIndex for extremely long names
return load(name).dynamicGetIndex(valueType, flags, isMethod);
}
@ -2229,8 +2229,8 @@ public class MethodEmitter implements Emitter {
}
popType(Type.SCOPE);
method.visitInvokeDynamicInsn((isMethod ? "dyn:getMethod|getProp|getElem:" : "dyn:getProp|getElem|getMethod:") +
NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags);
method.visitInvokeDynamicInsn(dynGetOperation(isMethod, isIndex) + ':' + NameCodec.encode(name),
Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags);
pushType(type);
convert(valueType); //most probably a nop
@ -2243,8 +2243,9 @@ public class MethodEmitter implements Emitter {
*
* @param name name of property
* @param flags call site flags
* @param isIndex is this an index operation?
*/
void dynamicSet(final String name, final int flags) {
void dynamicSet(final String name, final int flags, final boolean isIndex) {
if (name.length() > LARGE_STRING_THRESHOLD) { // use setIndex for extremely long names
load(name).swap().dynamicSetIndex(flags);
return;
@ -2261,7 +2262,8 @@ public class MethodEmitter implements Emitter {
popType(type);
popType(Type.SCOPE);
method.visitInvokeDynamicInsn("dyn:setProp|setElem:" + NameCodec.encode(name), methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags);
method.visitInvokeDynamicInsn(dynSetOperation(isIndex) + ':' + NameCodec.encode(name),
methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags);
}
/**
@ -2294,7 +2296,7 @@ public class MethodEmitter implements Emitter {
final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index);
method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod", signature, LINKERBOOTSTRAP, flags);
method.visitInvokeDynamicInsn(dynGetOperation(isMethod, true), signature, LINKERBOOTSTRAP, flags);
pushType(resultType);
if (result.isBoolean()) {
@ -2508,6 +2510,18 @@ public class MethodEmitter implements Emitter {
}
}
private static String dynGetOperation(final boolean isMethod, final boolean isIndex) {
if (isMethod) {
return isIndex ? "dyn:getMethod|getElem|getProp" : "dyn:getMethod|getProp|getElem";
} else {
return isIndex ? "dyn:getElem|getProp|getMethod" : "dyn:getProp|getElem|getMethod";
}
}
private static String dynSetOperation(final boolean isIndex) {
return isIndex ? "dyn:setElem|setProp" : "dyn:setProp|setElem";
}
private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) {
final Type from = conversion.getFrom();
final Type to = conversion.getTo();

View File

@ -30,13 +30,18 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.MessageDigest;
@ -49,6 +54,7 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
@ -388,6 +394,8 @@ public final class OptimisticTypesPersistence {
final File dir = new File(dirStr);
return "dev-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(getLastModifiedClassFile(
dir, 0L)));
} else if(protocol.equals("jrt")) {
return getJrtVersionDirName();
} else {
throw new AssertionError();
}
@ -546,4 +554,40 @@ public final class OptimisticTypesPersistence {
}
return Math.max(0, Integer.parseInt(str));
}
// version directory name if nashorn is loaded from jrt:/ URL
private static String getJrtVersionDirName() throws Exception {
final FileSystem fs = getJrtFileSystem();
// consider all .class resources under nashorn module to compute checksum
final Path nashorn = fs.getPath("/jdk.scripting.nashorn");
if (! Files.isDirectory(nashorn)) {
throw new FileNotFoundException("missing /jdk.scripting.nashorn dir in jrt fs");
}
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
Files.walk(nashorn).forEach(new Consumer<Path>() {
@Override
public void accept(Path p) {
// take only the .class resources.
if (Files.isRegularFile(p) && p.toString().endsWith(".class")) {
try {
digest.update(Files.readAllBytes(p));
} catch (final IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
}
});
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest.digest());
}
// get the default jrt FileSystem instance
private static FileSystem getJrtFileSystem() {
return AccessController.doPrivileged(
new PrivilegedAction<FileSystem>() {
@Override
public FileSystem run() {
return FileSystems.getFileSystem(URI.create("jrt:/"));
}
});
}
}

View File

@ -156,7 +156,7 @@ class SharedScopeCall {
assert !isCall || valueType.isObject(); // Callables are always objects
// If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously
// only apply to the call.
method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall);
method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall, false);
// If this is a get we're done, otherwise call the value as function.
if (isCall) {

View File

@ -28,6 +28,8 @@ package jdk.nashorn.internal.ir;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;
/**
* IR representation of a property access (period operator.)
@ -101,6 +103,14 @@ public final class AccessNode extends BaseNode {
return property;
}
/**
* Return true if this node represents an index operation normally represented as {@link IndexNode}.
* @return true if an index access.
*/
public boolean isIndex() {
return Token.descType(getToken()) == TokenType.LBRACKET;
}
private AccessNode setBase(final Expression base) {
if (this.base == base) {
return this;

View File

@ -341,10 +341,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
if (visitor.enterBinaryNode(this)) {
if(tokenType().isLeftAssociative()) {
return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor)));
}
return visitor.leaveBinaryNode(setRHS((Expression)rhs.accept(visitor)).setLHS((Expression)lhs.accept(visitor)));
return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor)));
}
return this;

View File

@ -27,7 +27,6 @@ package jdk.nashorn.internal.ir;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -468,11 +467,7 @@ public class RuntimeNode extends Expression implements Optimistic {
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
if (visitor.enterRuntimeNode(this)) {
final List<Expression> newArgs = new ArrayList<>();
for (final Node arg : args) {
newArgs.add((Expression)arg.accept(visitor));
}
return visitor.leaveRuntimeNode(setArgs(newArgs));
return visitor.leaveRuntimeNode(setArgs(Node.accept(visitor, args)));
}
return this;

View File

@ -48,6 +48,10 @@ public final class SwitchNode extends BreakableStatement {
/** Switch default index. */
private final int defaultCaseIndex;
/** True if all cases are 32-bit signed integer constants, without repetitions. It's a prerequisite for
* using a tableswitch/lookupswitch when generating code. */
private final boolean uniqueInteger;
/** Tag symbol. */
private Symbol tag;
@ -66,15 +70,17 @@ public final class SwitchNode extends BreakableStatement {
this.expression = expression;
this.cases = cases;
this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
this.uniqueInteger = false;
}
private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases,
final int defaultCaseIndex, final LocalVariableConversion conversion) {
final int defaultCaseIndex, final LocalVariableConversion conversion, final boolean uniqueInteger) {
super(switchNode, conversion);
this.expression = expression;
this.cases = cases;
this.defaultCaseIndex = defaultCaseIndex;
this.tag = switchNode.getTag(); //TODO are symbols inhereted as references?
this.tag = switchNode.getTag(); //TODO are symbols inherited as references?
this.uniqueInteger = uniqueInteger;
}
@Override
@ -83,7 +89,7 @@ public final class SwitchNode extends BreakableStatement {
for (final CaseNode caseNode : cases) {
newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion()));
}
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion));
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion, uniqueInteger));
}
@Override
@ -151,7 +157,7 @@ public final class SwitchNode extends BreakableStatement {
if (this.cases == cases) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
}
/**
@ -183,7 +189,7 @@ public final class SwitchNode extends BreakableStatement {
if (this.expression == expression) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
}
/**
@ -205,25 +211,30 @@ public final class SwitchNode extends BreakableStatement {
}
/**
* Returns true if all cases of this switch statement are 32-bit signed integer constants.
* @return true if all cases of this switch statement are 32-bit signed integer constants.
* Returns true if all cases of this switch statement are 32-bit signed integer constants, without repetitions.
* @return true if all cases of this switch statement are 32-bit signed integer constants, without repetitions.
*/
public boolean isInteger() {
for (final CaseNode caseNode : cases) {
final Expression test = caseNode.getTest();
if (test != null && !isIntegerLiteral(test)) {
return false;
}
public boolean isUniqueInteger() {
return uniqueInteger;
}
/**
* Sets whether all cases of this switch statement are 32-bit signed integer constants, without repetitions.
* @param lc lexical context
* @param uniqueInteger if true, all cases of this switch statement have been determined to be 32-bit signed
* integer constants, without repetitions.
* @return this switch node, if the value didn't change, or a new switch node with the changed value
*/
public SwitchNode setUniqueInteger(final LexicalContext lc, final boolean uniqueInteger) {
if(this.uniqueInteger == uniqueInteger) {
return this;
}
return true;
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
}
@Override
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion, uniqueInteger));
}
private static boolean isIntegerLiteral(final Expression expr) {
return expr instanceof LiteralNode && ((LiteralNode<?>)expr).getValue() instanceof Integer;
}
}

View File

@ -53,6 +53,7 @@ import static jdk.nashorn.internal.parser.TokenType.RPAREN;
import static jdk.nashorn.internal.parser.TokenType.SEMICOLON;
import static jdk.nashorn.internal.parser.TokenType.TERNARY;
import static jdk.nashorn.internal.parser.TokenType.WHILE;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.ArrayList;
@ -2659,7 +2660,7 @@ loop:
// name is null, generate anonymous name
boolean isAnonymous = false;
if (name == null) {
final String tmpName = getDefaultValidFunctionName(functionLine);
final String tmpName = getDefaultValidFunctionName(functionLine, isStatement);
name = new IdentNode(functionToken, Token.descPosition(functionToken), tmpName);
isAnonymous = true;
}
@ -2671,9 +2672,13 @@ loop:
final ParserContextFunctionNode functionNode = createParserContextFunctionNode(name, functionToken, FunctionNode.Kind.NORMAL, functionLine, parameters);
lc.push(functionNode);
Block functionBody = null;
// Hide the current default name across function boundaries. E.g. "x3 = function x1() { function() {}}"
// If we didn't hide the current default name, then the innermost anonymous function would receive "x3".
hideDefaultName();
try{
functionBody = functionBody(functionNode);
} finally {
defaultNames.pop();
lc.pop(functionNode);
}
@ -2754,9 +2759,17 @@ loop:
return function;
}
private String getDefaultValidFunctionName(final int functionLine) {
private String getDefaultValidFunctionName(final int functionLine, final boolean isStatement) {
final String defaultFunctionName = getDefaultFunctionName();
return isValidIdentifier(defaultFunctionName) ? defaultFunctionName : ANON_FUNCTION_PREFIX.symbolName() + functionLine;
if (isValidIdentifier(defaultFunctionName)) {
if (isStatement) {
// The name will be used as the LHS of a symbol assignment. We add the anonymous function
// prefix to ensure that it can't clash with another variable.
return ANON_FUNCTION_PREFIX.symbolName() + defaultFunctionName;
}
return defaultFunctionName;
}
return ANON_FUNCTION_PREFIX.symbolName() + functionLine;
}
private static boolean isValidIdentifier(final String name) {
@ -2790,6 +2803,10 @@ loop:
private void markDefaultNameUsed() {
defaultNames.pop();
hideDefaultName();
}
private void hideDefaultName() {
// Can be any value as long as getDefaultFunctionName doesn't recognize it as something it can extract a value
// from. Can't be null
defaultNames.push("");

View File

@ -2001,12 +2001,11 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
if (find == null) {
switch (operator) {
case "getElem": // getElem only gets here if element name is constant, so treat it like a property access
case "getProp":
return noSuchProperty(desc, request);
case "getMethod":
return noSuchMethod(desc, request);
case "getElem":
return createEmptyGetter(desc, explicitInstanceOfCheck, name);
default:
throw new AssertionError(operator); // never invoked with any other operation
}
@ -2333,8 +2332,9 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
}
final ScriptFunction func = (ScriptFunction)value;
final Object thiz = scopeCall && func.isStrict() ? ScriptRuntime.UNDEFINED : this;
final Object thiz = scopeCall && func.isStrict() ? UNDEFINED : this;
// TODO: It'd be awesome if we could bind "name" without binding "this".
// Since we're binding this we must use an identity guard here.
return new GuardedInvocation(
MH.dropArguments(
MH.constant(
@ -2342,9 +2342,9 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
func.makeBoundFunction(thiz, new Object[] { name })),
0,
Object.class),
NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck),
(SwitchPoint)null,
explicitInstanceOfCheck ? null : ClassCastException.class);
NashornGuards.combineGuards(
NashornGuards.getIdentityGuard(this),
NashornGuards.getMapGuard(getMap(), true)));
}
/**
@ -3710,7 +3710,9 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
final ScriptObject clone = (ScriptObject) super.clone();
if (objectSpill != null) {
clone.objectSpill = objectSpill.clone();
clone.primitiveSpill = primitiveSpill.clone();
if (primitiveSpill != null) {
clone.primitiveSpill = primitiveSpill.clone();
}
}
clone.arrayData = arrayData.copy();
return clone;

View File

@ -29,6 +29,7 @@ import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObject
import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_GETSLOT;
import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETMEMBER;
import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETSLOT;
import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_CALL;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import jdk.internal.dynalink.CallSiteDescriptor;
@ -131,6 +132,8 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker {
case "setProp":
case "setElem":
return c > 2 ? findSetMethod(desc) : findSetIndexMethod();
case "call":
return findCallMethod(desc);
default:
return null;
}
@ -156,6 +159,11 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker {
return new GuardedInvocation(JSOBJECTLINKER_PUT, IS_JSOBJECT_GUARD);
}
private static GuardedInvocation findCallMethod(final CallSiteDescriptor desc) {
final MethodHandle call = MH.insertArguments(JSOBJECT_CALL, 1, "call");
return new GuardedInvocation(MH.asCollector(call, Object[].class, desc.getMethodType().parameterCount() - 1), IS_JSOBJECT_GUARD);
}
@SuppressWarnings("unused")
private static boolean isJSObject(final Object self) {
return jsObjectClass.isInstance(self);
@ -215,6 +223,7 @@ final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker {
static final MethodHandle JSOBJECT_GETSLOT = findJSObjectMH_V("getSlot", Object.class, int.class).asType(MH.type(Object.class, Object.class, int.class));
static final MethodHandle JSOBJECT_SETMEMBER = findJSObjectMH_V("setMember", Void.TYPE, String.class, Object.class).asType(MH.type(Void.TYPE, Object.class, String.class, Object.class));
static final MethodHandle JSOBJECT_SETSLOT = findJSObjectMH_V("setSlot", Void.TYPE, int.class, Object.class).asType(MH.type(Void.TYPE, Object.class, int.class, Object.class));
static final MethodHandle JSOBJECT_CALL = findJSObjectMH_V("call", Object.class, String.class, Object[].class).asType(MH.type(Object.class, Object.class, String.class, Object[].class));
private static MethodHandle findJSObjectMH_V(final String name, final Class<?> rtype, final Class<?>... types) {
checkJSObjectClass();

View File

@ -33,7 +33,22 @@
var NullProvider = Java.type("jdk.nashorn.test.models.NullProvider");
if (!NullProvider.getBoolean()) { print("yay"); }
print(NullProvider.getLong() * (1 << 33));
print(NullProvider.getDouble() / 2.5);
print(NullProvider.getInteger() << 1);
try {
if (!NullProvider.getBoolean()) { print("yay"); }
print(NullProvider.getLong() * (1 << 33));
print(NullProvider.getDouble() / 2.5);
print(NullProvider.getInteger() << 1);
} catch (e if e instanceof java.lang.NullPointerException) {
var st = e.stackTrace;
if (st.length > 0 &&
st[0].className.equals("sun.invoke.util.ValueConversions")) {
// buggy JVM. ignore NPE and pass vacuously
// print to match .EXPECTED output
print("yay");
print(0);
print(0);
print(0);
} else {
throw e;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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-8066221: anonymous function statement name clashes with another symbol
* (compile-only test)
*
* @test
*/
x3 = function x1(x3) { function (){} };

View File

@ -0,0 +1,38 @@
/*
* 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-8066224: fixes for folding a constant-test ternary operator
*
* @test
* @run
*/
print((function(){
if(false ? 0 : '') {
throw false;
} else if (x = this) {
var x = x;
}
return x === this;
})())

View File

@ -0,0 +1 @@
true

View File

@ -0,0 +1,36 @@
/*
* 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-8066225: NPE in MethodEmitter with duplicate integer switch cases
*
* @test
* @run
*/
(function (x){
switch(x) {
case 44: for (var x in {}) {x}; print("1");
case 44: print("2");
}
})(44);

View File

@ -0,0 +1,2 @@
1
2

View File

@ -0,0 +1,40 @@
/*
* 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-8066227: CodeGenerator load unitialized slot
*
* @test
* @run
*/
print((function () { var x; (x += x = 0); return x; })());
print((function () { var x; (x -= x = 0); return x; })());
print((function () { var x; (x *= x = 0); return x; })());
print((function () { var x; (x /= x = 0); return x; })());
print((function () { var x; (x %= x = 0); return x; })());
print((function () { var x; (x <<= x = 0); return x; })());
print((function () { var x; (x >>= x = 0); return x; })());
print((function () { var x; (x >>>= x = 0); return x; })());
print((function () { var x; (x |= x = 0); return x; })());
print((function () { var x; (x &= x = 0); return x; })());

View File

@ -0,0 +1,10 @@
NaN
NaN
NaN
NaN
NaN
0
0
0
0
0

View File

@ -0,0 +1,34 @@
/*
* 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-8066230: Undefined object type assertion when computing TypeBounds
*
* @test
* @run
*/
(function() { void null + 0; })();
(function() { var x; x += void x; })();
(function() { var a = true + x, x; })();
print("SUCCESS");

View File

@ -0,0 +1 @@
SUCCESS

View File

@ -0,0 +1,46 @@
/*
* 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-8066236: RuntimeNode forces copy creation on visitation
*
* @test
* @run
*/
// Note: we're using Function("code") instead of (function(){ code }) so that
// we don't trigger parser API validation in JDK-8008448 tests. The test code
// encapsulated in functions below can't be correctly handled by the parser API
// currently, as it contains parser-generated REFERENCE_ERROR runtime nodes.
try {
Function("L: {this = x;break L}")();
} catch (e) {
print("threw ReferenceError: " + (e instanceof ReferenceError));
}
try {
Function("L:with(this--)break L;")();
} catch (e) {
print("threw ReferenceError: " + (e instanceof ReferenceError));
}
Function("L:with(Object in Object)break L;")();
print("SUCCESS");

View File

@ -0,0 +1,3 @@
threw ReferenceError: true
threw ReferenceError: true
SUCCESS

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2010, 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-8066669: dust.js performance regression caused by primitive field conversion
*
* @test
* @run
*/
// Make sure index access on Java objects is working as expected.
var map = new java.util.HashMap();
map["foo"] = "bar";
map[1] = 2;
map[false] = true;
map[null] = 0;
print(map);
var keys = map.keySet().iterator();
while(keys.hasNext()) {
var key = keys.next();
print(typeof key, key);
}
print(typeof map["foo"], map["foo"]);
print(typeof map[1], map[1]);
print(typeof map[false], map[false]);
print(typeof map[null], map[null]);
print(map.foo);
print(map.false);
print(map.null);
map.foo = "baz";
print(map);

View File

@ -0,0 +1,13 @@
{null=0, 1=2, false=true, foo=bar}
object null
number 1
boolean false
string foo
string bar
number 2
boolean true
number 0
bar
null
null
{null=0, 1=2, false=true, foo=baz}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2010, 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-8066932: __noSuchMethod__ binds to this-object without proper guard
*
* @test
* @run
*/
function C(id) {
this.id = id;
}
C.prototype.__noSuchMethod__ = function(name, args) {
return this.id;
};
function test(id) {
var c = new C(id);
return c.nonExistingMethod();
}
for (var i = 0; i < 30; i++) {
if (test(i) !== i) {
throw new Error("Wrong result from noSuchMethod in iteration " + i);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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-8067136: BrowserJSObjectLinker does not handle call on JSObjects
*
* @test
* @option -scripting
* @run
*/
// call on netscape.javascript.JSObject
function main() {
var JSObject;
try {
JSObject = Java.type("netscape.javascript.JSObject");
} catch (e) {
if (e instanceof java.lang.ClassNotFoundException) {
// pass vacuously by emitting the .EXPECTED file content
var str = readFully(__DIR__ + "JDK-8067136.js.EXPECTED");
print(str.substring(0, str.length - 1));
return;
} else{
fail("unexpected exception for JSObject", e);
}
}
test(JSObject);
}
function test(JSObject) {
var obj = new (Java.extend(JSObject))() {
getMember: function(name) {
if (name == "func") {
return new (Java.extend(JSObject)) {
call: function(n) {
print("func called");
}
}
}
return name.toUpperCase();
},
};
obj.func();
}
main();

View File

@ -0,0 +1 @@
func called

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2010, 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-8067219: NPE in ScriptObject.clone() when running with object fields
*
* @test
* @run
* @fork
* @option -Dnashorn.fields.objects=true
* @option --language=es6
*/
"use strict";
for (let i = 0; i < 10; i++) {
print(i);
}
try {
print(i);
} catch (e) {
print(e);
}
let a = [];
for (let i = 0; i < 10; i++) {
a.push(function() { print(i); });
}
a.forEach(function(f) { f(); });
a = [];
for (let i = 0; i < 10; i++) {
if (i == 5) {
i = "foo";
}
a.push(function() { print(i); });
}
a.forEach(function(f) { f(); });
try {
print(i);
} catch (e) {
print(e);
}
a = [];
for (let i = 0; i < 20; i++) {
if (i % 2 == 1) {
i += 2;
continue;
}
a.push(function() { print(i); });
}
a.forEach(function(f) { f(); });

View File

@ -0,0 +1,33 @@
0
1
2
3
4
5
6
7
8
9
ReferenceError: "i" is not defined
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
foo
ReferenceError: "i" is not defined
0
4
8
12
16

View File

@ -26,7 +26,8 @@
*
* @test
* @run
* @option --language=es6 */
* @option --language=es6
*/
"use strict";

View File

@ -10,7 +10,7 @@ bar
l[0]=foo
l[1]=a
l[0.9]=null
l['blah']=null
l['blah']=undefined
l[size_name]()=2
java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
java.lang.IndexOutOfBoundsException: Index: Infinity, Size: 2