From 18ac28e76f627d1ece50a0da257a67ca4d9a41c6 Mon Sep 17 00:00:00 2001 From: Marcus Lagergren Date: Thu, 19 Jun 2014 10:46:31 +0200 Subject: [PATCH 01/17] 8047078: Fuzzing bug discovered when ArrayLiteralNodes weren't immutable Reviewed-by: attila, sundar --- .../internal/codegen/CompilationPhase.java | 15 +- .../codegen/LocalVariableTypesCalculator.java | 10 +- .../nashorn/internal/codegen/Splitter.java | 2 +- .../nashorn/internal/codegen/WeighNodes.java | 1 - .../jdk/nashorn/internal/ir/LiteralNode.java | 370 ++++++++++-------- nashorn/test/script/basic/JDK-8047057.js | 12 +- nashorn/test/script/basic/JDK-8047078.js | 38 ++ 7 files changed, 277 insertions(+), 171 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8047078.js diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java index aba9962373d..c415d0cec6d 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java @@ -173,7 +173,18 @@ enum CompilationPhase { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L); - final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true); + + FunctionNode newFunctionNode; + + //ensure elementTypes, postsets and presets exist for splitter and arraynodes + newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + @Override + public LiteralNode leaveLiteralNode(final LiteralNode literalNode) { + return literalNode.initialize(lc); + } + }); + + newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true); assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName()); @@ -374,7 +385,7 @@ enum CompilationPhase { assert newUnit != null; newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi())); } - aln.setUnits(newArrayUnits); + return aln.setUnits(lc, newArrayUnits); } return node; } diff --git a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 5a1f09ae1ec..7112a53eb76 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -39,6 +39,7 @@ 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; @@ -63,7 +64,6 @@ import jdk.nashorn.internal.ir.LabelNode; 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; @@ -1207,10 +1207,10 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ @Override public Node leaveLiteralNode(final LiteralNode literalNode) { - if(literalNode instanceof ArrayLiteralNode) { - ((ArrayLiteralNode)literalNode).analyze(); - } - return literalNode; + //for e.g. ArrayLiteralNodes the initial types may have been narrowed due to the + //introduction of optimistic behavior - hence ensure that all literal nodes are + //reinitialized + return literalNode.initialize(lc); } @Override diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java index 088e49e1b66..9405d062faa 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java @@ -307,7 +307,7 @@ final class Splitter extends NodeVisitor { units.add(new ArrayUnit(unit, lo, postsets.length)); } - arrayLiteralNode.setUnits(units); + return arrayLiteralNode.setUnits(lc, units); } return literal; diff --git a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java index 7f735a2933b..26660a3a859 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java @@ -173,7 +173,6 @@ final class WeighNodes extends NodeOperatorVisitor { if (functionNode == topFunction) { // the function being weighted; descend into its statements return true; -// functionNode.visitStatements(this); } // just a reference to inner function from outer function weight += FUNC_EXPR_WEIGHT; diff --git a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java index e83901e4442..b71e450389f 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java +++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java @@ -29,6 +29,7 @@ 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; @@ -86,6 +87,17 @@ public abstract class LiteralNode extends Expression implements PropertyKey { this.value = newValue; } + /** + * Initialization setter, if required for immutable state. This is used for + * things like ArrayLiteralNodes that need to carry state for the splitter. + * Default implementation is just a nop. + * @param lc lexical context + * @return new literal node with initialized state, or same if nothing changed + */ + public LiteralNode initialize(final LexicalContext lc) { + return this; + } + /** * Check if the literal value is null * @return true if literal value is null @@ -573,24 +585,26 @@ public abstract class LiteralNode extends Expression implements PropertyKey { /** * Array literal node class. */ + @Immutable public static final class ArrayLiteralNode extends LiteralNode implements LexicalContextNode { /** Array element type. */ - private Type elementType; + private final Type elementType; /** Preset constant array. */ - private Object presets; + private final Object presets; /** Indices of array elements requiring computed post sets. */ - private int[] postsets; + private final int[] postsets; - private List units; + /** Sub units with indexes ranges, in which to split up code generation, for large literals */ + private final List units; /** * An ArrayUnit is a range in an ArrayLiteral. ArrayLiterals can * be split if they are too large, for bytecode generation reasons */ - public static class ArrayUnit { + public static final class ArrayUnit { /** Compile unit associated with the postsets range. */ private final CompileUnit compileUnit; @@ -634,6 +648,150 @@ public abstract class LiteralNode extends Expression implements PropertyKey { } } + private static final class ArrayLiteralInitializer { + + static ArrayLiteralNode initialize(final ArrayLiteralNode node) { + final Type elementType = computeElementType(node.value, node.elementType); + final int[] postsets = computePostsets(node.value); + final Object presets = computePresets(node.value, elementType, postsets); + return new ArrayLiteralNode(node, node.value, elementType, postsets, presets, node.units); + } + + private static Type computeElementType(final Expression[] value, final Type elementType) { + Type widestElementType = Type.INT; + + for (final Expression elem : value) { + if (elem == null) { + widestElementType = widestElementType.widest(Type.OBJECT); //no way to represent undefined as number + break; + } + + final Type type = elem.getType().isUnknown() ? Type.OBJECT : elem.getType(); + if (type.isBoolean()) { + //TODO fix this with explicit boolean types + widestElementType = widestElementType.widest(Type.OBJECT); + break; + } + + widestElementType = widestElementType.widest(type); + if (widestElementType.isObject()) { + break; + } + } + return widestElementType; + } + + private static int[] computePostsets(final Expression[] value) { + final int[] computed = new int[value.length]; + int nComputed = 0; + + for (int i = 0; i < value.length; i++) { + final Expression element = value[i]; + if (element == null || objectAsConstant(element) == POSTSET_MARKER) { + computed[nComputed++] = i; + } + } + return Arrays.copyOf(computed, nComputed); + } + + private static boolean setArrayElement(final int[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).intValue(); + return true; + } + return false; + } + + private static boolean setArrayElement(final long[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).longValue(); + return true; + } + return false; + } + + private static boolean setArrayElement(final double[] array, final int i, final Object n) { + if (n instanceof Number) { + array[i] = ((Number)n).doubleValue(); + return true; + } + return false; + } + + private static int[] presetIntArray(final Expression[] value, final int[] postsets) { + final int[] array = new int[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static long[] presetLongArray(final Expression[] value, final int[] postsets) { + final long[] array = new long[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static double[] presetDoubleArray(final Expression[] value, final int[] postsets) { + final double[] array = new double[value.length]; + int nComputed = 0; + for (int i = 0; i < value.length; i++) { + if (!setArrayElement(array, i, objectAsConstant(value[i]))) { + assert postsets[nComputed++] == i; + } + } + assert postsets.length == nComputed; + return array; + } + + private static Object[] presetObjectArray(final Expression[] value, final int[] postsets) { + final Object[] array = new Object[value.length]; + int nComputed = 0; + + for (int i = 0; i < value.length; i++) { + final Node node = value[i]; + + if (node == null) { + assert postsets[nComputed++] == i; + continue; + } + final Object element = objectAsConstant(node); + + if (element != POSTSET_MARKER) { + array[i] = element; + } else { + assert postsets[nComputed++] == i; + } + } + + assert postsets.length == nComputed; + return array; + } + + static Object computePresets(final Expression[] value, final Type elementType, final int[] postsets) { + assert !elementType.isUnknown(); + if (elementType.isInteger()) { + return presetIntArray(value, postsets); + } else if (elementType.isLong()) { + return presetLongArray(value, postsets); + } else if (elementType.isNumeric()) { + return presetDoubleArray(value, postsets); + } else { + return presetObjectArray(value, postsets); + } + } + } + /** * Constructor * @@ -644,136 +802,21 @@ public abstract class LiteralNode extends Expression implements PropertyKey { protected ArrayLiteralNode(final long token, final int finish, final Expression[] value) { super(Token.recast(token, TokenType.ARRAY), finish, value); this.elementType = Type.UNKNOWN; + this.presets = null; + this.postsets = null; + this.units = null; } /** * Copy constructor * @param node source array literal node */ - private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value) { + private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value, final Type elementType, final int[] postsets, final Object presets, final List units) { super(node, value); - this.elementType = node.elementType; - this.presets = node.presets; - this.postsets = node.postsets; - this.units = node.units; - } - - /** - * Compute things like widest element type needed. Internal use from compiler only - */ - public void analyze() { - assert elementType.isUnknown(); - elementType = getNarrowestElementType(value); - } - - private int[] presetIntArray() { - final int[] array = new int[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).intValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private long[] presetLongArray() { - final long[] array = new long[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).longValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private double[] presetNumberArray() { - final double[] array = new double[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Object element = objectAsConstant(value[i]); - - if (element instanceof Number) { - array[i] = ((Number)element).doubleValue(); - } else { - assert getPostsets()[nComputed++] == i; - } - } - - assert getPostsets().length == nComputed; - return array; - } - - private Object[] presetObjectArray() { - final Object[] array = new Object[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Node node = value[i]; - - if (node == null) { - assert getPostsets()[nComputed++] == i; - } else { - final Object element = objectAsConstant(node); - - if (element != POSTSET_MARKER) { - array[i] = element; - } else { - assert getPostsets()[nComputed++] == i; - } - } - } - - assert getPostsets().length == nComputed; - return array; - } - - /** - * Returns the narrowest element type that is wide enough to represent all the expressions in the array. - * @param elementExpressions the array of expressions - * @return the narrowest element type that is wide enough to represent all the expressions in the array. - */ - private static Type getNarrowestElementType(final Expression[] elementExpressions) { - Type widestElementType = Type.INT; - for (final Expression element : elementExpressions) { - if (element == null) { - widestElementType = widestElementType.widest(Type.OBJECT); //no way to represent undefined as number - break; - } - - Type elementType = element.getType(); - if (elementType.isUnknown()) { - elementType = Type.OBJECT; - } - - if (elementType.isBoolean()) { - widestElementType = widestElementType.widest(Type.OBJECT); - break; - } - - widestElementType = widestElementType.widest(elementType); - - if (widestElementType.isObject()) { - break; - } - } - return widestElementType; + this.elementType = elementType; + this.postsets = postsets; + this.presets = presets; + this.units = units; } @Override @@ -781,6 +824,19 @@ public abstract class LiteralNode extends Expression implements PropertyKey { return value; } + /** + * Setter that initializes all code generation meta data for an + * ArrayLiteralNode. This acts a setter, so the return value may + * return a new node and must be handled + * + * @param lc lexical context + * @return new array literal node with postsets, presets and element types initialized + */ + @Override + public ArrayLiteralNode initialize(final LexicalContext lc) { + return Node.replaceInLexicalContext(lc, this, ArrayLiteralInitializer.initialize(this)); + } + /** * Get the array element type as Java format, e.g. [I * @return array element type @@ -811,7 +867,7 @@ public abstract class LiteralNode extends Expression implements PropertyKey { * @return element type */ public Type getElementType() { - assert !elementType.isUnknown(); + assert !elementType.isUnknown() : this + " has elementType=unknown"; return elementType; } @@ -821,38 +877,28 @@ public abstract class LiteralNode extends Expression implements PropertyKey { * @return post set indices */ public int[] getPostsets() { - if(postsets == null) { - final int[] computed = new int[value.length]; - int nComputed = 0; - - for (int i = 0; i < value.length; i++) { - final Expression element = value[i]; - if(element == null || objectAsConstant(element) == POSTSET_MARKER) { - computed[nComputed++] = i; - } - } - postsets = Arrays.copyOf(computed, nComputed); - } + assert postsets != null : this + " elementType=" + elementType + " has no postsets"; return postsets; } + private boolean presetsMatchElementType() { + if (elementType == Type.INT) { + return presets instanceof int[]; + } else if (elementType == Type.LONG) { + return presets instanceof long[]; + } else if (elementType == Type.NUMBER) { + return presets instanceof double[]; + } else { + return presets instanceof Object[]; + } + } + /** * Get presets constant array * @return presets array, always returns an array type */ public Object getPresets() { - if(presets == null) { - final Type type = getElementType(); - if (type.isInteger()) { - presets = presetIntArray(); - } else if (type.isLong()) { - presets = presetLongArray(); - } else if (type.isNumeric()) { - presets = presetNumberArray(); - } else { - presets = presetObjectArray(); - } - } + assert presets != null && presetsMatchElementType() : this + " doesn't have presets, or invalid preset type: " + presets; return presets; } @@ -867,11 +913,16 @@ public abstract class LiteralNode extends Expression implements PropertyKey { /** * Set the ArrayUnits that make up this ArrayLiteral + * @param lc lexical context * @see ArrayUnit * @param units list of array units + * @return new or changed arrayliteralnode */ - public void setUnits(final List units) { - this.units = units; + public ArrayLiteralNode setUnits(final LexicalContext lc, final List units) { + if (this.units == units) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units)); } @Override @@ -889,8 +940,15 @@ public abstract class LiteralNode extends Expression implements PropertyKey { return this; } + private ArrayLiteralNode setValue(final LexicalContext lc, final Expression[] value) { + if (this.value == value) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units)); + } + private ArrayLiteralNode setValue(final LexicalContext lc, final List value) { - return (ArrayLiteralNode)lc.replace(this, new ArrayLiteralNode(this, value.toArray(new Expression[value.size()]))); + return setValue(lc, value.toArray(new Expression[value.size()])); } @Override diff --git a/nashorn/test/script/basic/JDK-8047057.js b/nashorn/test/script/basic/JDK-8047057.js index cf1263a1243..3ce588bfe6f 100644 --- a/nashorn/test/script/basic/JDK-8047057.js +++ b/nashorn/test/script/basic/JDK-8047057.js @@ -29,8 +29,7 @@ */ // commented out makeFuncAndCall calls are still result in crash -// Tests commented with //** fail only within test framework. -// Pass fine with standalone "jjs" mode. +// Tests commented with //** fail only when assertions are turned on function makeFuncAndCall(code) { Function(code)(); @@ -52,19 +51,19 @@ makeFuncAndCall("L: { while(0) break L; return; }"); makeFuncExpectError("L: {while(0) break L; return [](); }", TypeError); // makeFuncAndCall("do with({}) break ; while(0);"); makeFuncAndCall("while(0) with({}) continue ;"); -//** makeFuncAndCall("eval([]);"); -//** makeFuncAndCall("try{} finally{[]}"); +makeFuncAndCall("eval([]);"); +makeFuncAndCall("try{} finally{[]}"); makeFuncAndCall("try { } catch(x if 1) { try { } catch(x2) { } }"); makeFuncAndCall("try { } catch(x if 1) { try { return; } catch(x2) { { } } }"); makeFuncAndCall("Error() * (false)[-0]--"); makeFuncAndCall("try { var x = 1, x = null; } finally { }"); makeFuncAndCall("try { var x = {}, x = []; } catch(x3) { }"); -//** makeFuncAndCall("[delete this]"); +makeFuncAndCall("[delete this]"); // makeFuncAndCall("if(eval('', eval('', function() {}))) { }"); // makeFuncAndCall("if(eval('', eval('', function() {}))) { }"); // makeFuncAndCall("eval(\"[,,];\", [11,12,13,14].some)"); // makeFuncAndCall("eval(\"1.2e3\", ({})[ /x/ ])"); -// makeFuncAndCall("eval(\"x4\", x3);"); +makeFuncExpectError("eval(\"x4\", x3);", ReferenceError); makeFuncAndCall("with({5.0000000000000000000000: String()}){(false); }"); makeFuncAndCall("try { var x = undefined, x = 5.0000000000000000000000; } catch(x) { x = undefined; }"); makeFuncAndCall("(function (x){ x %= this}(false))"); @@ -73,3 +72,4 @@ makeFuncAndCall("(false % !this) && 0"); makeFuncAndCall("with({8: 'fafafa'.replace()}){ }"); makeFuncAndCall("(function (x) '' )(true)"); makeFuncExpectError("new eval(function(){})", TypeError); +//** makeFuncAndCall('eval("23", ({})[/x/])'); diff --git a/nashorn/test/script/basic/JDK-8047078.js b/nashorn/test/script/basic/JDK-8047078.js new file mode 100644 index 00000000000..984e4f29abc --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047078.js @@ -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-8047078: ArrayLiteral mutability caused trouble in optimistic types + * + * @test + * @run + */ + +function makeFuncAndCall(code) { + Function(code)(); +} + +makeFuncAndCall("eval([]);"); +makeFuncAndCall("eval([1]);"); +makeFuncAndCall("eval([1,2,3,,4]);"); +makeFuncAndCall("try{} finally{[]}"); From 2bdfb1233c343cf8de1accb210dceffbf3115cc5 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Thu, 19 Jun 2014 20:36:03 +0530 Subject: [PATCH 02/17] 8047369: Add regression tests for passing test cases of JDK-8024971 Reviewed-by: hannesw, jlaskey --- nashorn/test/script/basic/JDK-8047369.js | 186 +++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 nashorn/test/script/basic/JDK-8047369.js diff --git a/nashorn/test/script/basic/JDK-8047369.js b/nashorn/test/script/basic/JDK-8047369.js new file mode 100644 index 00000000000..dec55152bd5 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047369.js @@ -0,0 +1,186 @@ +/* + * 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-8047369: Add regression tests for passing test cases of JDK-8024971 + * + * @test + * @run + * @option -scripting + */ + +function makeFuncAndCall(code) { + Function(code)(); +} + +function makeFuncExpectError(code, ErrorType) { + try { + makeFuncAndCall(code); + } catch (e) { + if (! (e instanceof ErrorType)) { + fail(ErrorType.name + " expected, got " + e); + } + } +} + +function evalExpectError(code, ErrorType) { + try { + eval(code)(); + } catch (e) { + if (! (e instanceof ErrorType)) { + fail(ErrorType.name + " expected, got " + e); + } + } +} + +function evalExpectValue(code, value) { + if (eval(code) != value) { + fail("Expected " + value + " with eval of " + code); + } +} + +makeFuncAndCall("for(x.x in 0) {}"); +// bug JDK-8047357 +// makeFuncAndCall("switch((null >> x3)) { default: {var x; break ; }\nthrow x; }"); +makeFuncExpectError("switch(x) { case 8: break; case false: }", ReferenceError); +makeFuncAndCall("try { return true; } finally { return false; } "); +makeFuncAndCall("({ get 1e81(){} })"); +makeFuncAndCall("{var x, x3;try { return 0; } finally { return 3/0; } }"); +makeFuncExpectError("with(x ? 1e81 : (x2.constructor = 0.1)) {}", ReferenceError); +makeFuncAndCall("while(x-=1) {var x=0; }"); +makeFuncAndCall("while((x-=false) && 0) { var x = this; }"); +makeFuncAndCall("/*infloop*/while(x4-=x) var x, x4 = x1;"); +makeFuncAndCall("/*infloop*/L:while(x+=null) { this;var x = /x/g ; }"); +makeFuncAndCall("while((x1|=0.1) && 0) { var x1 = -0, functional; }"); +makeFuncAndCall("with({}) return (eval(\"arguments\"));"); + +evalExpectValue(< Date: Fri, 20 Jun 2014 12:25:00 +0200 Subject: [PATCH 03/17] 8046921: Deoptimization type information peristence Reviewed-by: hannesw, lagergren --- nashorn/make/project.properties | 1 + .../internal/codegen/CodeGenerator.java | 36 +- .../internal/codegen/CompilationPhase.java | 4 +- .../nashorn/internal/codegen/Compiler.java | 32 +- .../codegen/OptimisticTypesPersistence.java | 345 ++++++++++++++++++ .../jdk/nashorn/internal/codegen/TypeMap.java | 15 + .../internal/runtime/CompiledFunction.java | 10 +- .../RecompilableScriptFunctionData.java | 69 +++- .../script/basic/JDK-8030182_2.js.EXPECTED | 2 +- 9 files changed, 484 insertions(+), 30 deletions(-) create mode 100644 nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java diff --git a/nashorn/make/project.properties b/nashorn/make/project.properties index eb708a41320..77f8ed73bf7 100644 --- a/nashorn/make/project.properties +++ b/nashorn/make/project.properties @@ -279,6 +279,7 @@ run.test.jvmargs.common=\ -Dfile.encoding=UTF-8 \ -Duser.language=${run.test.user.language} \ -Duser.country=${run.test.user.country} \ + -Dnashorn.typeInfo.cacheDir=${build.dir}${file.separator}test${file.separator}type_info_cache \ ${jfr.args} \ -XX:+HeapDumpOnOutOfMemoryError diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java index 21b51b334c3..984440f39bf 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -145,6 +145,7 @@ import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.Scope; +import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; @@ -1821,19 +1822,40 @@ final class CodeGenerator extends NodeOperatorVisitor 0) { + b.append('-'); + for(final Type t: paramTypes) { + b.append(t.getBytecodeStackType()); + } + } + return new LocationDescriptor(new File(cacheDir, b.toString())); + } + + private static final class LocationDescriptor { + private final File file; + + LocationDescriptor(final File file) { + this.file = file; + } + } + + + /** + * Stores the map of optimistic types for a given function. + * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling + * {@link #getLocationDescriptor(Source, int, Type[])}. + * @param optimisticTypes the map of optimistic types. + */ + @SuppressWarnings("resource") + public static void store(final Object locationDescriptor, final Map optimisticTypes) { + if(locationDescriptor == null) { + return; + } + final File file = ((LocationDescriptor)locationDescriptor).file; + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + synchronized(getFileLock(file)) { + try (final FileOutputStream out = new FileOutputStream(file);) { + out.getChannel().lock(); // lock exclusive + final DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(out)); + dout.writeInt(optimisticTypes.size()); + for(Map.Entry e: optimisticTypes.entrySet()) { + dout.writeInt(e.getKey()); + final byte typeChar; + final Type type = e.getValue(); + if(type == Type.OBJECT) { + typeChar = 'L'; + } else if(type == Type.NUMBER) { + typeChar = 'D'; + } else if(type == Type.LONG) { + typeChar = 'J'; + } else { + throw new AssertionError(); + } + dout.write(typeChar); + } + dout.flush(); + } catch(final Exception e) { + final long now = System.currentTimeMillis(); + if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { + getLogger().warning("Failed to write " + file, e); + lastReportedError = now; + } + } + } + return null; + } + }); + } + + /** + * Loads the map of optimistic types for a given function. + * @param locationDescriptor the opaque persistence location descriptor, retrieved by calling + * {@link #getLocationDescriptor(Source, int, Type[])}. + * @return the map of optimistic types, or null if persisted type information could not be retrieved. + */ + @SuppressWarnings("resource") + public static Map load(final Object locationDescriptor) { + if (locationDescriptor == null) { + return null; + } + final File file = ((LocationDescriptor)locationDescriptor).file; + + return AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public Map run() { + try { + if(!file.isFile()) { + return null; + } + synchronized(getFileLock(file)) { + try (final FileInputStream in = new FileInputStream(file);) { + in.getChannel().lock(0, Long.MAX_VALUE, true); // lock shared + final DataInputStream din = new DataInputStream(new BufferedInputStream(in)); + final Map map = new TreeMap<>(); + final int size = din.readInt(); + for(int i = 0; i < size; ++i) { + final int pp = din.readInt(); + final int typeChar = din.read(); + final Type type; + switch(typeChar) { + case 'L': type = Type.OBJECT; break; + case 'D': type = Type.NUMBER; break; + case 'J': type = Type.LONG; break; + default: throw new AssertionError(); + } + map.put(pp, type); + } + return map; + } + } + } catch (final Exception e) { + final long now = System.currentTimeMillis(); + if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { + getLogger().warning("Failed to read " + file, e); + lastReportedError = now; + } + return null; + } + } + }); + } + + private static File createCacheDir() { + if(Options.getBooleanProperty("nashorn.typeInfo.disabled")) { + return null; + } + try { + return createCacheDirPrivileged(); + } catch(final Exception e) { + getLogger().warning("Failed to create cache dir", e); + return null; + } + } + + private static File createCacheDirPrivileged() { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public File run() { + final String explicitDir = System.getProperty("nashorn.typeInfo.cacheDir"); + final File dir; + if(explicitDir != null) { + dir = new File(explicitDir); + } else { + // When no directory is explicitly specified, get an operating system specific cache directory, + // and create "com.oracle.java.NashornTypeInfo" in it. + dir = new File(getCacheDirBase(), "com.oracle.java.NashornTypeInfo"); + } + final String versionDirName; + try { + versionDirName = getVersionDirName(); + } catch(Exception e) { + getLogger().warning("Failed to calculate version dir name", e); + return null; + } + final File versionDir = new File(dir, versionDirName); + versionDir.mkdirs(); + if(versionDir.isDirectory()) { + getLogger().info("Optimistic type persistence directory is " + versionDir); + return versionDir; + } + getLogger().warning("Could not create optimistic type persistence directory " + versionDir); + return null; + } + }); + } + + /** + * Returns an operating system specific root directory for cache files. + * @return an operating system specific root directory for cache files. + */ + private static File getCacheDirBase() { + final String os = System.getProperty("os.name", "generic"); + if("Mac OS X".equals(os)) { + // Mac OS X stores caches in ~/Library/Caches + return new File(new File(System.getProperty("user.home"), "Library"), "Caches"); + } else if(os.startsWith("Windows")) { + // On Windows, temp directory is the best approximation of a cache directory, as its contents persist across + // reboots and various cleanup utilities know about it. java.io.tmpdir normally points to a user-specific + // temp directory, %HOME%\LocalSettings\Temp. + return new File(System.getProperty("java.io.tmpdir")); + } else { + // In all other cases we're presumably dealing with a UNIX flavor (Linux, Solaris, etc.); "~/.cache" + return new File(System.getProperty("user.home"), ".cache"); + } + } + + /** + * In order to ensure that changes in Nashorn code don't cause corruption in the data, we'll create a + * per-code-version directory. Normally, this will create the SHA-1 digest of the nashorn.jar. In case the classpath + * for nashorn is local directory (e.g. during development), this will create the string "dev-" followed by the + * timestamp of the most recent .class file. + * @return + */ + private static String getVersionDirName() throws Exception { + final URL url = OptimisticTypesPersistence.class.getResource(""); + final String protocol = url.getProtocol(); + if(protocol.equals("jar")) { + // Normal deployment: nashorn.jar + final String jarUrlFile = url.getFile(); + final String filePath = jarUrlFile.substring(0, jarUrlFile.indexOf('!')); + final URL file = new URL(filePath); + try (final InputStream in = file.openStream()) { + final byte[] buf = new byte[128*1024]; + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + for(;;) { + final int l = in.read(buf); + if(l == -1) { + return Base64.getUrlEncoder().encodeToString(digest.digest()); + } + digest.update(buf, 0, l); + } + } + } else if(protocol.equals("file")) { + // Development + final String fileStr = url.getFile(); + final String className = OptimisticTypesPersistence.class.getName(); + final int packageNameLen = className.lastIndexOf('.'); + final String dirStr = fileStr.substring(0, fileStr.length() - packageNameLen - 1); + final File dir = new File(dirStr); + return "dev-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(getLastModifiedClassFile(dir, 0L))); + } else { + throw new AssertionError(); + } + } + + private static long getLastModifiedClassFile(final File dir, final long max) { + long currentMax = max; + for(File f: dir.listFiles()) { + if(f.getName().endsWith(".class")) { + final long lastModified = f.lastModified(); + if(lastModified > currentMax) { + currentMax = lastModified; + } + } else if(f.isDirectory()) { + final long lastModified = getLastModifiedClassFile(f, currentMax); + if(lastModified > currentMax) { + currentMax = lastModified; + } + } + } + return currentMax; + } + + private static Object[] createLockArray() { + final Object[] lockArray = new Object[Runtime.getRuntime().availableProcessors() * 2]; + for(int i = 0; i < lockArray.length; ++i) { + lockArray[i] = new Object(); + } + return lockArray; + } + + private static Object getFileLock(final File file) { + return locks[(file.hashCode() & Integer.MAX_VALUE) % locks.length]; + } + + private static DebugLogger getLogger() { + try { + return Context.getContext().getLogger(RecompilableScriptFunctionData.class); + } catch (final Exception e) { + e.printStackTrace(); + return DebugLogger.DISABLED_LOGGER; + } + } +} diff --git a/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java b/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java index eb337f55245..11efce1a3db 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/TypeMap.java @@ -29,6 +29,7 @@ import java.lang.invoke.MethodType; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.NoSuchElementException; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.runtime.ScriptFunction; @@ -61,6 +62,20 @@ public class TypeMap { this.needsCallee = needsCallee; } + /** + * Returns the array of parameter types for a particular function node + * @param functionNodeId the ID of the function node + * @return an array of parameter types + * @throws NoSuchElementException if the type map has no mapping for the requested function + */ + public Type[] getParameterTypes(final int functionNodeId) { + final Type[] paramTypes = paramTypeMap.get(functionNodeId); + if (paramTypes == null) { + throw new NoSuchElementException(Integer.toString(functionNodeId)); + } + return paramTypes.clone(); + } + MethodType getCallSiteType(final FunctionNode functionNode) { final Type[] types = paramTypeMap.get(functionNode.getId()); if (types == null) { diff --git a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java index 6c1ca1ec757..87e32fc6508 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java @@ -89,11 +89,12 @@ final class CompiledFunction { this.log = log; } - CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, final int flags) { + CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, + final Map invalidatedProgramPoints, final int flags) { this(invoker, null, functionData.getLogger()); this.flags = flags; if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) { - optimismInfo = new OptimismInfo(functionData); + optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints); } else { optimismInfo = null; } @@ -691,13 +692,14 @@ final class CompiledFunction { private static class OptimismInfo { // TODO: this is pointing to its owning ScriptFunctionData. Re-evaluate if that's okay. private final RecompilableScriptFunctionData data; - private final Map invalidatedProgramPoints = new TreeMap<>(); + private final Map invalidatedProgramPoints; private SwitchPoint optimisticAssumptions; private final DebugLogger log; - OptimismInfo(final RecompilableScriptFunctionData data) { + OptimismInfo(final RecompilableScriptFunctionData data, final Map invalidatedProgramPoints) { this.data = data; this.log = data.getLogger(); + this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new TreeMap() : invalidatedProgramPoints; newOptimisticAssumptions(); } diff --git a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java index 78ee06e58ce..b2c0ed6b57f 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @@ -36,12 +36,14 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import jdk.internal.dynalink.support.NameCodec; import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.FunctionSignature; +import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; import jdk.nashorn.internal.codegen.TypeMap; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; @@ -390,7 +392,11 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null); } - Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, final ScriptObject runtimeScope, final Map ipp, final int[] cep) { + Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, + final ScriptObject runtimeScope, final Map invalidatedProgramPoints, + final int[] continuationEntryPoints) { + final TypeMap typeMap = typeMap(actualCallSiteType); + final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, typeMap == null ? null : typeMap.getParameterTypes(functionNodeId)); final Context context = Context.getContextTrusted(); return new Compiler( context, @@ -402,12 +408,31 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp true, // is on demand this, // compiledFunction, i.e. this RecompilableScriptFunctionData typeMap(actualCallSiteType), // type map - ipp, // invalidated program points - cep, // continuation entry points + getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points + typeInformationFile, + continuationEntryPoints, // continuation entry points runtimeScope); // runtime scope } - private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) { + /** + * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to + * load invalidated program points map from the persistent type info cache. + * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function + * doesn't have it. + * @param typeInformationFile the object describing the location of the persisted type information. + * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if + * neither an existing map or a persistent cached type info is available. + */ + private static Map getEffectiveInvalidatedProgramPoints( + final Map invalidatedProgramPoints, final Object typeInformationFile) { + if(invalidatedProgramPoints != null) { + return invalidatedProgramPoints; + } + final Map loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile); + return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap(); + } + + private TypeSpecializedFunction compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) { // We're creating an empty script object for holding local variables. AssignSymbols will populate it with // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and // CompilationEnvironment#declareLocalSymbol()). @@ -417,7 +442,20 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp } final FunctionNode fn = reparse(); - return getCompiler(fn, actualCallSiteType, runtimeScope).compile(fn, CompilationPhases.COMPILE_ALL); + final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope); + + final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL); + return new TypeSpecializedFunction(compiledFn, compiler.getInvalidatedProgramPoints()); + } + + private static class TypeSpecializedFunction { + private final FunctionNode fn; + private final Map invalidatedProgramPoints; + + TypeSpecializedFunction(final FunctionNode fn, final Map invalidatedProgramPoints) { + this.fn = fn; + this.invalidatedProgramPoints = invalidatedProgramPoints; + } } private MethodType explicitParams(final MethodType callSiteType) { @@ -494,14 +532,14 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp methodLocator = new MethodLocator(functionNode); } - private CompiledFunction addCode(final MethodHandle target, final int fnFlags) { - final CompiledFunction cfn = new CompiledFunction(target, this, fnFlags); + private CompiledFunction addCode(final MethodHandle target, final Map invalidatedProgramPoints, final int fnFlags) { + final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, fnFlags); code.add(cfn); return cfn; } private CompiledFunction addCode(final FunctionNode fn) { - return addCode(lookup(fn), fn.getFlags()); + return addCode(lookup(fn), null, fn.getFlags()); } /** @@ -510,11 +548,12 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups * for the same specialization, so we must adapt the handle to the expected type. - * @param fn the function + * @param tfn the function * @param callSiteType the call site type * @return the compiled function object, with its type matching that of the call site type. */ - private CompiledFunction addCode(final FunctionNode fn, final MethodType callSiteType) { + private CompiledFunction addCode(final TypeSpecializedFunction tfn, final MethodType callSiteType) { + final FunctionNode fn = tfn.fn; if (fn.isVarArg()) { return addCode(fn); } @@ -544,7 +583,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp toType = toType.dropParameterTypes(fromCount, toCount); } - return addCode(lookup(fn).asType(toType), fn.getFlags()); + return addCode(lookup(fn).asType(toType), tfn.invalidatedProgramPoints, fn.getFlags()); } @@ -555,7 +594,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp if (existingBest == null) { if(code.isEmpty() && methodLocator != null) { // This is a deserialized object, reconnect from method handle - existingBest = addCode(methodLocator.getMethodHandle(), methodLocator.getFunctionFlags()); + existingBest = addCode(methodLocator.getMethodHandle(), null, methodLocator.getFunctionFlags()); } else { existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType); } @@ -576,9 +615,9 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp } if (applyToCall) { - final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope); - if (fn.hasOptimisticApplyToCall()) { //did the specialization work - existingBest = addCode(fn, callSiteType); + final TypeSpecializedFunction tfn = compileTypeSpecialization(callSiteType, runtimeScope); + if (tfn.fn.hasOptimisticApplyToCall()) { //did the specialization work + existingBest = addCode(tfn, callSiteType); } } diff --git a/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED b/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED index 772e504d917..090bd1f1ab2 100644 --- a/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED +++ b/nashorn/test/script/basic/JDK-8030182_2.js.EXPECTED @@ -1,3 +1,3 @@ ReferenceError: "g" is not defined - at (test/script/basic/JDK-8030182_2.js#42:4@0:-1) + at (test/script/basic/JDK-8030182_2.js#42:4@1:-1) at (test/script/basic/JDK-8030182_2.js:42) From 7aa808a993ed1d666d4abe759dc33376fcd95edb Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Mon, 23 Jun 2014 10:59:33 +0200 Subject: [PATCH 04/17] 8046905: apply on apply is broken Reviewed-by: hannesw, lagergren --- .../internal/runtime/ScriptFunction.java | 78 +++++++++++++++- .../internal/runtime/ScriptObject.java | 28 +++--- nashorn/test/script/basic/JDK-8046905.js | 91 +++++++++++++++++++ .../test/script/basic/JDK-8046905.js.EXPECTED | 41 +++++++++ nashorn/test/script/basic/JDK-8047057.js | 2 +- 5 files changed, 224 insertions(+), 16 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8046905.js create mode 100644 nashorn/test/script/basic/JDK-8046905.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java index 0d2fcd00a75..ca98f4047b1 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java @@ -35,6 +35,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; +import java.util.Collections; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -593,6 +594,12 @@ public abstract class ScriptFunction extends ScriptObject { private GuardedInvocation createApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, final LinkRequest request, final Object[] args) { final MethodType descType = desc.getMethodType(); final int paramCount = descType.parameterCount(); + if(descType.parameterType(paramCount - 1).isArray()) { + // This is vararg invocation of apply or call. This can normally only happen when we do a recursive + // invocation of createApplyOrCallCall (because we're doing apply-of-apply). In this case, create delegate + // linkage by unpacking the vararg invocation and use pairArguments to introduce the necessary spreader. + return createVarArgApplyOrCallCall(isApply, desc, request, args); + } final boolean passesThis = paramCount > 2; final boolean passesArgs = paramCount > 3; @@ -647,6 +654,7 @@ public abstract class ScriptFunction extends ScriptObject { System.arraycopy(args, 3, tmp, 0, tmp.length); appliedArgs[2] = NativeFunction.toApplyArgs(tmp); } else { + assert !isApply; System.arraycopy(args, 3, appliedArgs, 2, args.length - 3); } } else if (isFailedApplyToCall) { @@ -705,8 +713,7 @@ public abstract class ScriptFunction extends ScriptObject { } final MethodType guardType = guard.type(); - // Original function guard will expect the invoked function in parameter position 0, but we're passing it in - // position 1. + // We need to account for the dropped (apply|call) function argument. guard = MH.dropArguments(guard, 0, descType.parameterType(0)); // Take the "isApplyFunction" guard, and bind it to this function. MethodHandle applyFnGuard = MH.insertArguments(IS_APPLY_FUNCTION, 2, this); @@ -718,7 +725,72 @@ public abstract class ScriptFunction extends ScriptObject { return appliedInvocation.replaceMethods(inv, guard); } - private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) { + /* + * This method is used for linking nested apply. Specialized apply and call linking will create a variable arity + * call site for an apply call; when createApplyOrCallCall sees a linking request for apply or call with + * Nashorn-style variable arity call site (last argument type is Object[]) it'll delegate to this method. + * This method converts the link request from a vararg to a non-vararg one (unpacks the array), then delegates back + * to createApplyOrCallCall (with which it is thus mutually recursive), and adds appropriate argument spreaders to + * invocation and the guard of whatever createApplyOrCallCall returned to adapt it back into a variable arity + * invocation. It basically reduces the problem of vararg call site linking of apply and call back to the (already + * solved by createApplyOrCallCall) non-vararg call site linking. + */ + private GuardedInvocation createVarArgApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, + final LinkRequest request, final Object[] args) { + final MethodType descType = desc.getMethodType(); + final int paramCount = descType.parameterCount(); + final Object[] varArgs = (Object[])args[paramCount - 1]; + // -1 'cause we're not passing the vararg array itself + final int copiedArgCount = args.length - 1; + int varArgCount = varArgs.length; + + // Spread arguments for the delegate createApplyOrCallCall invocation. + final Object[] spreadArgs = new Object[copiedArgCount + varArgCount]; + System.arraycopy(args, 0, spreadArgs, 0, copiedArgCount); + System.arraycopy(varArgs, 0, spreadArgs, copiedArgCount, varArgCount); + + // Spread call site descriptor for the delegate createApplyOrCallCall invocation. We drop vararg array and + // replace it with a list of Object.class. + final MethodType spreadType = descType.dropParameterTypes(paramCount - 1, paramCount).appendParameterTypes( + Collections.>nCopies(varArgCount, Object.class)); + final CallSiteDescriptor spreadDesc = desc.changeMethodType(spreadType); + + // Delegate back to createApplyOrCallCall with the spread (that is, reverted to non-vararg) request/ + final LinkRequest spreadRequest = request.replaceArguments(spreadDesc, spreadArgs); + final GuardedInvocation spreadInvocation = createApplyOrCallCall(isApply, spreadDesc, spreadRequest, spreadArgs); + + // Add spreader combinators to returned invocation and guard. + return spreadInvocation.replaceMethods( + // Use standard ScriptObject.pairArguments on the invocation + pairArguments(spreadInvocation.getInvocation(), descType), + // Use our specialized spreadGuardArguments on the guard (see below). + spreadGuardArguments(spreadInvocation.getGuard(), descType)); + } + + private static MethodHandle spreadGuardArguments(final MethodHandle guard, final MethodType descType) { + final MethodType guardType = guard.type(); + final int guardParamCount = guardType.parameterCount(); + final int descParamCount = descType.parameterCount(); + final int spreadCount = guardParamCount - descParamCount + 1; + if (spreadCount <= 0) { + // Guard doesn't dip into the varargs + return guard; + } + + final MethodHandle arrayConvertingGuard; + // If the last parameter type of the guard is an array, then it is already itself a guard for a vararg apply + // invocation. We must filter the last argument with toApplyArgs otherwise deeper levels of nesting will fail + // with ClassCastException of NativeArray to Object[]. + if(guardType.parameterType(guardParamCount - 1).isArray()) { + arrayConvertingGuard = MH.filterArguments(guard, guardParamCount - 1, NativeFunction.TO_APPLY_ARGS); + } else { + arrayConvertingGuard = guard; + } + + return ScriptObject.adaptHandleToVarArgCallSite(arrayConvertingGuard, descParamCount); + } + + private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) { final MethodHandle bound; if(fn instanceof ScriptFunction && ((ScriptFunction)fn).needsWrappedThis()) { bound = MH.filterArguments(mh, 1, SCRIPTFUNCTION_GLOBALFILTER); diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java index 1e095d45905..90003b38f67 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java @@ -2499,18 +2499,7 @@ public abstract class ScriptObject implements PropertyAccess { } if (isCallerVarArg) { - final int spreadArgs = parameterCount - callCount + 1; - return MH.filterArguments( - MH.asSpreader( - methodHandle, - Object[].class, - spreadArgs), - callCount - 1, - MH.insertArguments( - TRUNCATINGFILTER, - 0, - spreadArgs) - ); + return adaptHandleToVarArgCallSite(methodHandle, callCount); } if (callCount < parameterCount) { @@ -2541,6 +2530,21 @@ public abstract class ScriptObject implements PropertyAccess { return methodHandle; } + static MethodHandle adaptHandleToVarArgCallSite(final MethodHandle mh, final int callSiteParamCount) { + final int spreadArgs = mh.type().parameterCount() - callSiteParamCount + 1; + return MH.filterArguments( + MH.asSpreader( + mh, + Object[].class, + spreadArgs), + callSiteParamCount - 1, + MH.insertArguments( + TRUNCATINGFILTER, + 0, + spreadArgs) + ); + } + @SuppressWarnings("unused") private static Object[] truncatingFilter(final int n, final Object[] array) { final int length = array == null ? 0 : array.length; diff --git a/nashorn/test/script/basic/JDK-8046905.js b/nashorn/test/script/basic/JDK-8046905.js new file mode 100644 index 00000000000..231e2a1d803 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8046905.js @@ -0,0 +1,91 @@ +/* + * 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-8046905: apply on apply is broken + * + * @test + * @run + */ + +var apply = Function.prototype.apply; +var call = Function.prototype.call; +var sort = Array.prototype.sort; +var join = Array.prototype.join; + +// Running three times so that we test an already linked call site too: +// i==0: linking initially with assumed optimistic returned type int. +// i==1: linking after deoptimization with returned type Object. +// i==2: re-running code linked in previous iteration. This will +// properly exercise the guards too. +print("1 level of apply") +for(i = 0; i < 3; ++i) { + print(sort.apply([4,3,2,1])) +} +print("2 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(sort,[[4,3,2,1]])) +} +print("3 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[sort,[[4,3,2,1]]])) +} +print("4 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[sort,[[4,3,2,1]]]])) +} +print("5 levels of apply") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[apply,[sort,[[4,3,2,1]]]]])) +} +print("Many levels of apply!") +for(i = 0; i < 3; ++i) { + print(apply.apply(apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[apply,[sort,[[4,3,2,1]]]]]]]]]]]]]]]]]]]]]])) +} + +print("different invocations that'll trigger relinking") +var invocation = [sort,[[4,3,2,1]]]; +for(i = 0; i < 4; ++i) { + print(apply.apply(apply,[apply,invocation])) + // First change after i==1, so it relinks an otherwise stable linkage + if(i == 1) { + invocation = [sort,[[8,7,6,5]]]; + } else if(i == 2) { + invocation = [join,[[8,7,6,5],["-"]]]; + } +} + +print("Many levels of call!") +for(i = 0; i < 3; ++i) { + print(call.call(call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,call,sort,[4,3,2,1])) +} + +print("call apply call apply call... a lot"); +for(i = 0; i < 3; ++i) { + print(apply.call(call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [sort, [4,3,2,1]]]]]]])) +} + +print("apply call apply call apply... a lot"); +for(i = 0; i < 3; ++i) { + print(call.apply(apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, apply, [call, sort, [[4,3,2,1]]]]]]]]])) +} diff --git a/nashorn/test/script/basic/JDK-8046905.js.EXPECTED b/nashorn/test/script/basic/JDK-8046905.js.EXPECTED new file mode 100644 index 00000000000..0cee6789f61 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8046905.js.EXPECTED @@ -0,0 +1,41 @@ +1 level of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +2 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +3 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +4 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +5 levels of apply +1,2,3,4 +1,2,3,4 +1,2,3,4 +Many levels of apply! +1,2,3,4 +1,2,3,4 +1,2,3,4 +different invocations that'll trigger relinking +1,2,3,4 +1,2,3,4 +5,6,7,8 +8-7-6-5 +Many levels of call! +1,2,3,4 +1,2,3,4 +1,2,3,4 +call apply call apply call... a lot +1,2,3,4 +1,2,3,4 +1,2,3,4 +apply call apply call apply... a lot +1,2,3,4 +1,2,3,4 +1,2,3,4 diff --git a/nashorn/test/script/basic/JDK-8047057.js b/nashorn/test/script/basic/JDK-8047057.js index 3ce588bfe6f..84183f19059 100644 --- a/nashorn/test/script/basic/JDK-8047057.js +++ b/nashorn/test/script/basic/JDK-8047057.js @@ -67,7 +67,7 @@ makeFuncExpectError("eval(\"x4\", x3);", ReferenceError); makeFuncAndCall("with({5.0000000000000000000000: String()}){(false); }"); makeFuncAndCall("try { var x = undefined, x = 5.0000000000000000000000; } catch(x) { x = undefined; }"); makeFuncAndCall("(function (x){ x %= this}(false))"); -// makeFuncAndCall("eval.apply.apply(function(){ eval('') })"); +makeFuncAndCall("eval.apply.apply(function(){ eval('') })"); makeFuncAndCall("(false % !this) && 0"); makeFuncAndCall("with({8: 'fafafa'.replace()}){ }"); makeFuncAndCall("(function (x) '' )(true)"); From 90b667d86525815976d58be88ea5e0fa74cacac5 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Mon, 23 Jun 2014 21:23:53 +0530 Subject: [PATCH 05/17] 8047728: (function(x){var o={x:0}; with(o){delete x} return o.x})() evaluates to 0 instead of undefined Reviewed-by: jlaskey, hannesw, attila --- nashorn/bin/jjsdebug.sh | 25 +++++++++ nashorn/make/nbproject/ide-targets.xml | 2 +- .../internal/codegen/AssignSymbols.java | 2 +- .../codegen/LocalVariableTypesCalculator.java | 6 ++- nashorn/test/script/basic/JDK-8047728.js | 54 +++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 nashorn/bin/jjsdebug.sh create mode 100644 nashorn/test/script/basic/JDK-8047728.js diff --git a/nashorn/bin/jjsdebug.sh b/nashorn/bin/jjsdebug.sh new file mode 100644 index 00000000000..509700c39bf --- /dev/null +++ b/nashorn/bin/jjsdebug.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# 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. +# + +$JAVA_HOME/bin/jjs -J-Djava.ext.dirs=`dirname $0`/../dist -J-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=y $* diff --git a/nashorn/make/nbproject/ide-targets.xml b/nashorn/make/nbproject/ide-targets.xml index a592cff6a07..d1e8135f101 100644 --- a/nashorn/make/nbproject/ide-targets.xml +++ b/nashorn/make/nbproject/ide-targets.xml @@ -34,7 +34,7 @@ - + diff --git a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java index 4a817fae3db..65e65c785a1 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -702,7 +702,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements // If this is a declared variable or a function parameter, delete always fails (except for globals). final String name = ident.getName(); final Symbol symbol = ident.getSymbol(); - final boolean failDelete = strictMode || symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()); + final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))); if (failDelete && symbol.isThis()) { return LiteralNode.newInstance(unaryNode, true).accept(this); diff --git a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 7112a53eb76..5ffe4b7f66b 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -583,7 +583,11 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ } } setCompilerConstantAsObject(functionNode, CompilerConstants.THIS); - if(functionNode.needsParentScope()) { + + // NOTE: coarse-grained. If we wanted to solve it completely precisely, + // we'd also need to push/pop its type when handling WithNode (so that + // it can go back to undefined after a 'with' block. + if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) { setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE); } if(functionNode.needsCallee()) { diff --git a/nashorn/test/script/basic/JDK-8047728.js b/nashorn/test/script/basic/JDK-8047728.js new file mode 100644 index 00000000000..ba36ccd5058 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047728.js @@ -0,0 +1,54 @@ +/* + * 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-8047728: (function(x){var o={x:0}; with(o){delete x} return o.x})() evaluates to 0 instead of undefined + * + * @test + * @run + */ + +function func(x) { + var o = {x:0}; + with(o){ + delete x; + } + return o.x +} + +if (typeof func() != 'undefined') { + fail("expected undefined from 'func' call"); +} + +function func2() { + var x; + var o = {x:0}; + with(o){ + delete x; + } + return o.x +} + +if (typeof func2() != 'undefined') { + fail("expected undefined from 'func2' call"); +} From 3ad3dd1d894c690a425e04c2499ecb6618e2c625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 23 Jun 2014 18:32:11 +0200 Subject: [PATCH 06/17] 8046201: Avoid repeated flattening of nested ConsStrings Reviewed-by: lagergren, attila --- .../nashorn/internal/runtime/ConsString.java | 43 ++++-- .../internal/runtime/ConsStringTest.java | 135 ++++++++++++++++++ 2 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java b/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java index 8f764f4bdda..41e2c49e938 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java @@ -36,8 +36,12 @@ import java.util.Deque; public final class ConsString implements CharSequence { private CharSequence left, right; - final private int length; - private boolean flat = false; + private final int length; + private volatile int state = STATE_NEW; + + private final static int STATE_NEW = 0; + private final static int STATE_THRESHOLD = 2; + private final static int STATE_FLATTENED = -1; /** * Constructor @@ -57,7 +61,7 @@ public final class ConsString implements CharSequence { @Override public String toString() { - return (String) flattened(); + return (String) flattened(true); } @Override @@ -67,22 +71,31 @@ public final class ConsString implements CharSequence { @Override public char charAt(final int index) { - return flattened().charAt(index); + return flattened(true).charAt(index); } @Override public CharSequence subSequence(final int start, final int end) { - return flattened().subSequence(start, end); + return flattened(true).subSequence(start, end); } - private CharSequence flattened() { - if (!flat) { - flatten(); + /** + * Returns the components of this ConsString as a {@code CharSequence} array with two elements. + * The elements will be either {@code Strings} or other {@code ConsStrings}. + * @return CharSequence array of length 2 + */ + public synchronized CharSequence[] getComponents() { + return new CharSequence[] { left, right }; + } + + private CharSequence flattened(boolean flattenNested) { + if (state != STATE_FLATTENED) { + flatten(flattenNested); } return left; } - private void flatten() { + private synchronized void flatten(boolean flattenNested) { // We use iterative traversal as recursion may exceed the stack size limit. final char[] chars = new char[length]; int pos = length; @@ -97,8 +110,14 @@ public final class ConsString implements CharSequence { do { if (cs instanceof ConsString) { final ConsString cons = (ConsString) cs; - stack.addFirst(cons.left); - cs = cons.right; + // Count the times a cons-string is traversed as part of other cons-strings being flattened. + // If it crosses a threshold we flatten the nested cons-string internally. + if (cons.state == STATE_FLATTENED || (flattenNested && ++cons.state >= STATE_THRESHOLD)) { + cs = cons.flattened(false); + } else { + stack.addFirst(cons.left); + cs = cons.right; + } } else { final String str = (String) cs; pos -= str.length(); @@ -109,7 +128,7 @@ public final class ConsString implements CharSequence { left = new String(chars); right = ""; - flat = true; + state = STATE_FLATTENED; } } diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java b/nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java new file mode 100644 index 00000000000..9a10ab6d042 --- /dev/null +++ b/nashorn/test/src/jdk/nashorn/internal/runtime/ConsStringTest.java @@ -0,0 +1,135 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.runtime; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import org.testng.annotations.Test; + +/** + * Tests for JSType methods. + * + * @test + * @run testng jdk.nashorn.internal.runtime.ConsStringTest + */ +public class ConsStringTest { + + /** + * Test toString conversion + */ + @Test + public void testConsStringToString() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + final ConsString cs5 = new ConsString("a", cs4); + assertEquals(cs5.toString(), "abcdef"); + assertEquals(cs4.toString(), "bcdef"); + assertEquals(cs3.toString(), "bcde"); + assertEquals(cs2.toString(), "de"); + assertEquals(cs1.toString(), "bc"); + // ConsStrings should be flattened now + assertEquals(cs1.getComponents()[0], "bc"); + assertEquals(cs1.getComponents()[1], ""); + assertEquals(cs2.getComponents()[0], "de"); + assertEquals(cs2.getComponents()[1], ""); + assertEquals(cs3.getComponents()[0], "bcde"); + assertEquals(cs3.getComponents()[1], ""); + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + } + + /** + * Test charAt + */ + @Test + public void testConsStringCharAt() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + final ConsString cs5 = new ConsString("a", cs4); + assertEquals(cs1.charAt(1), 'c'); + assertEquals(cs2.charAt(0), 'd'); + assertEquals(cs3.charAt(3), 'e'); + assertEquals(cs4.charAt(1), 'c'); + assertEquals(cs5.charAt(2), 'c'); + // ConsStrings should be flattened now + assertEquals(cs1.getComponents()[0], "bc"); + assertEquals(cs1.getComponents()[1], ""); + assertEquals(cs2.getComponents()[0], "de"); + assertEquals(cs2.getComponents()[1], ""); + assertEquals(cs3.getComponents()[0], "bcde"); + assertEquals(cs3.getComponents()[1], ""); + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + } + + + /** + * Test flattening of top-level and internal ConsStrings + */ + @Test + public void testConsStringFlattening() { + final ConsString cs1 = new ConsString("b", "c"); + final ConsString cs2 = new ConsString("d", "e"); + final ConsString cs3 = new ConsString(cs1, cs2); + final ConsString cs4 = new ConsString(cs3, "f"); + + final ConsString cs5 = new ConsString("a", cs4); + // top-level ConsString should not yet be flattened + assert(cs5.getComponents()[0] == "a"); + assert(cs5.getComponents()[1] == cs4); + assertEquals(cs5.toString(), "abcdef"); + // top-level ConsString should be flattened + assertEquals(cs5.getComponents()[0], "abcdef"); + assertEquals(cs5.getComponents()[1], ""); + // internal ConsString should not yet be flattened after first traversal + assertEquals(cs4.getComponents()[0], cs3); + assertEquals(cs4.getComponents()[1], "f"); + + final ConsString cs6 = new ConsString("a", cs4); + // top-level ConsString should not yet be flattened + assertEquals(cs6.getComponents()[0], "a"); + assertEquals(cs6.getComponents()[1], cs4); + assertEquals(cs6.toString(), "abcdef"); + // top-level ConsString should be flattened + assertEquals(cs6.getComponents()[0], "abcdef"); + assertEquals(cs6.getComponents()[1], ""); + // internal ConsString should have been flattened after second traversal + assertEquals(cs4.getComponents()[0], "bcdef"); + assertEquals(cs4.getComponents()[1], ""); + } +} From 566786aba609c21e2e08d90c11fe8ef55eb544d3 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Tue, 24 Jun 2014 19:43:44 +0530 Subject: [PATCH 07/17] 8047959: bindings created for declarations in eval code are not mutable Reviewed-by: jlaskey, attila --- .../internal/codegen/CodeGenerator.java | 12 ++++ .../internal/codegen/FieldObjectCreator.java | 6 +- .../codegen/LocalVariableTypesCalculator.java | 2 +- .../nashorn/internal/codegen/MapCreator.java | 22 ++++--- .../jdk/nashorn/internal/objects/Global.java | 2 +- .../jdk/nashorn/internal/runtime/Context.java | 21 ++++++- .../jdk/nashorn/internal/runtime/Source.java | 61 ++++++++++++++++--- nashorn/test/script/basic/JDK-8047959.js | 59 ++++++++++++++++++ .../test/script/basic/JDK-8047959.js.EXPECTED | 15 +++++ 9 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8047959.js create mode 100644 nashorn/test/script/basic/JDK-8047959.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java index 984440f39bf..110e1109d4a 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -211,6 +211,9 @@ final class CodeGenerator extends NodeOperatorVisitor extends ObjectCreator { /** call site flags to be used for invocations */ private final int callSiteFlags; + /** are we creating this field object from 'eval' code? */ + private final boolean evalCode; /** * Constructor @@ -88,7 +90,7 @@ public abstract class FieldObjectCreator extends ObjectCreator { FieldObjectCreator(final CodeGenerator codegen, final List> tuples, final boolean isScope, final boolean hasArguments) { super(codegen, tuples, isScope, hasArguments); this.callSiteFlags = codegen.getCallSiteFlags(); - + this.evalCode = codegen.isEvalCode(); countFields(); findClass(); } @@ -153,7 +155,7 @@ public abstract class FieldObjectCreator extends ObjectCreator { @Override protected PropertyMap makeMap() { assert propertyMap == null : "property map already initialized"; - propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount); + propertyMap = newMapCreator(fieldObjectClass).makeFieldMap(hasArguments(), fieldCount, paddedFieldCount, evalCode); return propertyMap; } diff --git a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 5ffe4b7f66b..c372d952c0a 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -584,7 +584,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ } setCompilerConstantAsObject(functionNode, CompilerConstants.THIS); - // NOTE: coarse-grained. If we wanted to solve it completely precisely, + // TODO: coarse-grained. If we wanted to solve it completely precisely, // we'd also need to push/pop its type when handling WithNode (so that // it can go back to undefined after a 'with' block. if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) { diff --git a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java index 8e7cfa3fd78..d4800f8a892 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java @@ -63,13 +63,13 @@ public class MapCreator { /** * Constructs a property map based on a set of fields. * - * @param hasArguments does the created object have an "arguments" property + * @param hasArguments does the created object have an "arguments" property * @param fieldCount Number of fields in use. - * @param fieldMaximum Number of fields available. - * + * @param fieldMaximum Number of fields available. + * @param evalCode is this property map created for 'eval' code? * @return New map populated with accessor properties. */ - PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum) { + PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum, final boolean evalCode) { final List properties = new ArrayList<>(); assert tuples != null; @@ -79,7 +79,7 @@ public class MapCreator { final Class initialType = tuple.getValueType(); if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) { - final int flags = getPropertyFlags(symbol, hasArguments); + final int flags = getPropertyFlags(symbol, hasArguments, evalCode); final Property property = new AccessorProperty( key, flags, @@ -104,7 +104,7 @@ public class MapCreator { //TODO initial type is object here no matter what. Is that right? if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) { - final int flags = getPropertyFlags(symbol, hasArguments); + final int flags = getPropertyFlags(symbol, hasArguments, false); properties.add( new SpillProperty( key, @@ -124,7 +124,7 @@ public class MapCreator { * * @return flags to use for fields */ - static int getPropertyFlags(final Symbol symbol, final boolean hasArguments) { + static int getPropertyFlags(final Symbol symbol, final boolean hasArguments, final boolean evalCode) { int flags = 0; if (symbol.isParam()) { @@ -135,7 +135,13 @@ public class MapCreator { flags |= Property.HAS_ARGUMENTS; } - if (symbol.isScope()) { + // See ECMA 5.1 10.5 Declaration Binding Instantiation. + // Step 2 If code is eval code, then let configurableBindings + // be true else let configurableBindings be false. + // We have to make vars, functions declared in 'eval' code + // configurable. But vars, functions from any other code is + // not configurable. + if (symbol.isScope() && !evalCode) { flags |= Property.NOT_CONFIGURABLE; } diff --git a/nashorn/src/jdk/nashorn/internal/objects/Global.java b/nashorn/src/jdk/nashorn/internal/objects/Global.java index 469466fe93d..4e2ba68a603 100644 --- a/nashorn/src/jdk/nashorn/internal/objects/Global.java +++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java @@ -883,7 +883,7 @@ public final class Global extends ScriptObject implements Scope { final Global global = Global.instance(); final ScriptObject scope = self instanceof ScriptObject ? (ScriptObject)self : global; - return global.getContext().eval(scope, str.toString(), callThis, location, Boolean.TRUE.equals(strict)); + return global.getContext().eval(scope, str.toString(), callThis, location, Boolean.TRUE.equals(strict), true); } /** diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Context.java b/nashorn/src/jdk/nashorn/internal/runtime/Context.java index d521598986a..aae43b11818 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java @@ -560,12 +560,29 @@ public final class Context { * @param callThis "this" to be passed to the evaluated code * @param location location of the eval call * @param strict is this {@code eval} call from a strict mode code? + * @return the return value of the {@code eval} + */ + public Object eval(final ScriptObject initialScope, final String string, + final Object callThis, final Object location, final boolean strict) { + return eval(initialScope, string, callThis, location, strict, false); + } + + /** + * Entry point for {@code eval} + * + * @param initialScope The scope of this eval call + * @param string Evaluated code as a String + * @param callThis "this" to be passed to the evaluated code + * @param location location of the eval call + * @param strict is this {@code eval} call from a strict mode code? + * @param evalCall is this called from "eval" builtin? * * @return the return value of the {@code eval} */ - public Object eval(final ScriptObject initialScope, final String string, final Object callThis, final Object location, final boolean strict) { + public Object eval(final ScriptObject initialScope, final String string, + final Object callThis, final Object location, final boolean strict, final boolean evalCall) { final String file = location == UNDEFINED || location == null ? "" : location.toString(); - final Source source = sourceFor(file, string); + final Source source = sourceFor(file, string, evalCall); final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval? final Global global = Context.getGlobal(); ScriptObject scope = initialScope; diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Source.java b/nashorn/src/jdk/nashorn/internal/runtime/Source.java index b9f6fd0dba4..d64bbfa26e6 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/Source.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/Source.java @@ -142,29 +142,34 @@ public final class Source implements Loggable { long lastModified(); char[] array(); + + boolean isEvalCode(); } private static class RawData implements Data { private final char[] array; + private final boolean evalCode; private int hash; - private RawData(final char[] array) { + private RawData(final char[] array, final boolean evalCode) { this.array = Objects.requireNonNull(array); + this.evalCode = evalCode; } - private RawData(final String source) { + private RawData(final String source, final boolean evalCode) { this.array = Objects.requireNonNull(source).toCharArray(); + this.evalCode = evalCode; } private RawData(final Reader reader) throws IOException { - this(readFully(reader)); + this(readFully(reader), false); } @Override public int hashCode() { int h = hash; if (h == 0) { - h = hash = Arrays.hashCode(array); + h = hash = Arrays.hashCode(array) ^ (evalCode? 1 : 0); } return h; } @@ -175,7 +180,8 @@ public final class Source implements Loggable { return true; } if (obj instanceof RawData) { - return Arrays.equals(array, ((RawData)obj).array); + final RawData other = (RawData)obj; + return Arrays.equals(array, other.array) && evalCode == other.evalCode; } return false; } @@ -206,6 +212,10 @@ public final class Source implements Loggable { } + @Override + public boolean isEvalCode() { + return evalCode; + } } private static class URLData implements Data { @@ -287,6 +297,11 @@ public final class Source implements Loggable { return array; } + @Override + public boolean isEvalCode() { + return false; + } + boolean isDeferred() { return array == null; } @@ -368,6 +383,18 @@ public final class Source implements Loggable { return data.array(); } + /** + * Returns a Source instance + * + * @param name source name + * @param content contents as char array + * @param isEval does this represent code from 'eval' call? + * @return source instance + */ + public static Source sourceFor(final String name, final char[] content, final boolean isEval) { + return new Source(name, baseName(name), new RawData(content, isEval)); + } + /** * Returns a Source instance * @@ -377,7 +404,7 @@ public final class Source implements Loggable { * @return source instance */ public static Source sourceFor(final String name, final char[] content) { - return new Source(name, baseName(name), new RawData(content)); + return sourceFor(name, content, false); } /** @@ -385,11 +412,22 @@ public final class Source implements Loggable { * * @param name source name * @param content contents as string + * @param isEval does this represent code from 'eval' call? + * @return source instance + */ + public static Source sourceFor(final String name, final String content, final boolean isEval) { + return new Source(name, baseName(name), new RawData(content, isEval)); + } + + /** + * Returns a Source instance * + * @param name source name + * @param content contents as string * @return source instance */ public static Source sourceFor(final String name, final String content) { - return new Source(name, baseName(name), new RawData(content)); + return sourceFor(name, content, false); } /** @@ -554,6 +592,15 @@ public final class Source implements Loggable { return data.url(); } + /** + * Returns whether this source was submitted via 'eval' call or not. + * + * @return true if this source represents code submitted via 'eval' + */ + public boolean isEvalCode() { + return data.isEvalCode(); + } + /** * Find the beginning of the line containing position. * @param position Index to offending token. diff --git a/nashorn/test/script/basic/JDK-8047959.js b/nashorn/test/script/basic/JDK-8047959.js new file mode 100644 index 00000000000..9ad1c8d1a22 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047959.js @@ -0,0 +1,59 @@ +/* + * 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-8047959: bindings created for declarations in eval code are not mutable + * + * @test + * @run + */ + +eval("var x=10;"); +print('delete x? ' + delete x); +print('typeof x = ' + typeof x); + +eval("function f() {}"); +print('delete f? ' + delete f); +print('typeof f = ' + typeof f); + +var foo = 223; +print('delete foo? ' + delete foo); +print('typeof foo = ' + typeof foo); + +function func() {} +print('delete func? ' + delete func); +print('typeof func = ' + typeof func); + +eval("var foo = 33;"); +print("delete foo? " + delete foo); +print("typeof foo? " + typeof foo); +print("foo = " + foo); + +var x = "global"; +(function(){ + eval("var x='local'"); + print("x in function = "+ x); + print("delete x? = " + delete x); + print("x after delete = " + x); +})(); +print("x = " + x); diff --git a/nashorn/test/script/basic/JDK-8047959.js.EXPECTED b/nashorn/test/script/basic/JDK-8047959.js.EXPECTED new file mode 100644 index 00000000000..935f1002dfb --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047959.js.EXPECTED @@ -0,0 +1,15 @@ +delete x? false +typeof x = number +delete f? true +typeof f = undefined +delete foo? false +typeof foo = number +delete func? false +typeof func = function +delete foo? false +typeof foo? number +foo = 33 +x in function = local +delete x? = true +x after delete = global +x = global From d983b0b09c1717c2f7dc39a0b5e9903b712b6172 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Tue, 24 Jun 2014 19:29:41 +0200 Subject: [PATCH 08/17] 8048009: Type info caching accidentally defeated Reviewed-by: hannesw, jlaskey --- .../nashorn/internal/codegen/Compiler.java | 10 ++-------- .../codegen/OptimisticTypesPersistence.java | 20 +++++++++---------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java index 32888b0c2b0..f5281ba9d56 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java @@ -45,7 +45,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; @@ -122,11 +121,7 @@ public final class Compiler implements Loggable { * that using whatever was at program point 17 as an int failed. */ private final Map invalidatedProgramPoints; - /** - * The snapshot of invalidatedProgramPoints before the compilation. Used to compare it to invalidatedProgramPoints, - * and if the two are equal, not write the type information to a file. - */ - private final Map invalidatedProgramPointsOnEntry; + /** * Descriptor of the location where we write the type information after compilation. */ @@ -381,7 +376,6 @@ public final class Compiler implements Loggable { this.firstCompileUnitName = firstCompileUnitName(); this.strict = isStrict; - this.invalidatedProgramPointsOnEntry = typeInformationFile == null ? null : new HashMap<>(this.invalidatedProgramPoints); this.optimistic = env._optimistic_types; } @@ -537,7 +531,7 @@ public final class Compiler implements Loggable { time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L); } - if(typeInformationFile != null && !phases.isRestOfCompilation() && !Objects.equals(invalidatedProgramPoints, invalidatedProgramPointsOnEntry)) { + if(typeInformationFile != null && !phases.isRestOfCompilation()) { OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints); } diff --git a/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java index 317a3cf4f60..b90fa026ddb 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java @@ -136,11 +136,7 @@ public final class OptimisticTypesPersistence { } dout.flush(); } catch(final Exception e) { - final long now = System.currentTimeMillis(); - if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { - getLogger().warning("Failed to write " + file, e); - lastReportedError = now; - } + reportError("write", file, e); } } return null; @@ -190,17 +186,21 @@ public final class OptimisticTypesPersistence { } } } catch (final Exception e) { - final long now = System.currentTimeMillis(); - if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { - getLogger().warning("Failed to read " + file, e); - lastReportedError = now; - } + reportError("read", file, e); return null; } } }); } + private static void reportError(final String msg, final File file, final Exception e) { + final long now = System.currentTimeMillis(); + if(now - lastReportedError > ERROR_REPORT_THRESHOLD) { + getLogger().warning(String.format("Failed to %s %s", msg, file), e); + lastReportedError = now; + } + } + private static File createCacheDir() { if(Options.getBooleanProperty("nashorn.typeInfo.disabled")) { return null; From dee1364e7ce0abd7f6aa62e522262c09cc70b86a Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Wed, 25 Jun 2014 17:08:47 +0530 Subject: [PATCH 09/17] 8048071: eval within 'with' statement does not use correct scope if with scope expression has a copy of eval Reviewed-by: hannesw, jlaskey --- .../internal/codegen/CodeGenerator.java | 23 ++++- .../jdk/nashorn/internal/ir/IdentNode.java | 11 +++ nashorn/test/script/basic/JDK-8048071.js | 85 +++++++++++++++++++ .../test/script/basic/JDK-8048071.js.EXPECTED | 11 +++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8048071.js create mode 100644 nashorn/test/script/basic/JDK-8048071.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java index 110e1109d4a..de971ab0308 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -1290,13 +1290,26 @@ final class CodeGenerator extends NodeOperatorVisitor Date: Wed, 25 Jun 2014 14:36:24 +0200 Subject: [PATCH 10/17] 8048079: Persistent code store is broken after optimistic types merge Reviewed-by: sundar, jlaskey, attila --- .../internal/codegen/CompilationPhase.java | 8 +-- .../jdk/nashorn/internal/runtime/Context.java | 16 ++--- .../RecompilableScriptFunctionData.java | 70 +++---------------- nashorn/test/script/basic/JDK-8048079_1.js | 35 ++++++++++ .../script/basic/JDK-8048079_1.js.EXPECTED | 3 + nashorn/test/script/basic/JDK-8048079_2.js | 35 ++++++++++ .../script/basic/JDK-8048079_2.js.EXPECTED | 3 + .../runtime/CodeStoreAndPathTest.java | 2 +- 8 files changed, 96 insertions(+), 76 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8048079_1.js create mode 100644 nashorn/test/script/basic/JDK-8048079_1.js.EXPECTED create mode 100644 nashorn/test/script/basic/JDK-8048079_2.js create mode 100644 nashorn/test/script/basic/JDK-8048079_2.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java index 85b805060cc..8b3819266f4 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java @@ -502,7 +502,7 @@ enum CompilationPhase { Class rootClass = null; long length = 0L; - final CodeInstaller codeInstaller = compiler.getCodeInstaller(); + final CodeInstaller codeInstaller = compiler.getCodeInstaller(); final Map bytecode = compiler.getBytecode(); @@ -527,12 +527,10 @@ enum CompilationPhase { final Object[] constants = compiler.getConstantData().toArray(); codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants); - // index recompilable script function datas in the constant pool - final Map rfns = new IdentityHashMap<>(); + // initialize transient fields on recompilable script function data for (final Object constant: constants) { if (constant instanceof RecompilableScriptFunctionData) { - final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant; - rfns.put(rfn, rfn); + ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller); } } diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Context.java b/nashorn/src/jdk/nashorn/internal/runtime/Context.java index aae43b11818..a70f80d43d9 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java @@ -444,15 +444,11 @@ public final class Context { } if (env._persistent_cache) { - if (env._lazy_compilation || env._optimistic_types) { - getErr().println("Can not use persistent class caching with lazy compilation or optimistic compilation."); - } else { - try { - final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"); - codeStore = new CodeStore(cacheDir); - } catch (final IOException e) { - throw new RuntimeException("Error initializing code cache", e); - } + try { + final String cacheDir = Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"); + codeStore = new CodeStore(cacheDir); + } catch (final IOException e) { + throw new RuntimeException("Error initializing code cache", e); } } @@ -1179,7 +1175,7 @@ public final class Context { for (final Object constant : constants) { if (constant instanceof RecompilableScriptFunctionData) { - ((RecompilableScriptFunctionData) constant).setCodeAndSource(installedClasses, source); + ((RecompilableScriptFunctionData) constant).initTransients(source, installer); } } diff --git a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java index b2c0ed6b57f..fc14707730b 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @@ -28,7 +28,6 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.lookup.Lookup.MH; import java.io.IOException; -import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -56,7 +55,6 @@ import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; -import jdk.nashorn.internal.scripts.JS; /** * This is a subclass that represents a script function that may be regenerated, @@ -85,9 +83,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp /** Source from which FunctionNode was parsed. */ private transient Source source; - /** Allows us to retrieve the method handle for this function once the code is compiled */ - private MethodLocator methodLocator; - /** Token of this function within the source. */ private final long token; @@ -239,15 +234,18 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp } /** - * Setter for code and source + * Initialize transient fields on deserialized instances * - * @param code map of code, class name to class * @param source source + * @param installer code installer */ - public void setCodeAndSource(final Map> code, final Source source) { - this.source = source; - if (methodLocator != null) { - methodLocator.setClass(code.get(methodLocator.getClassName())); + public void initTransients(final Source source, final CodeInstaller installer) { + if (this.source == null && this.installer == null) { + this.source = source; + this.installer = installer; + } else if (this.source != source || this.installer != installer) { + // Existing values must be same as those passed as parameters + throw new IllegalArgumentException(); } } @@ -529,7 +527,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp throw new IllegalStateException(functionNode.getName() + " id=" + functionNode.getId()); } addCode(functionNode); - methodLocator = new MethodLocator(functionNode); } private CompiledFunction addCode(final MethodHandle target, final Map invalidatedProgramPoints, final int fnFlags) { @@ -592,12 +589,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp synchronized (code) { CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope); if (existingBest == null) { - if(code.isEmpty() && methodLocator != null) { - // This is a deserialized object, reconnect from method handle - existingBest = addCode(methodLocator.getMethodHandle(), null, methodLocator.getFunctionFlags()); - } else { - existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType); - } + existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType); } assert existingBest != null; @@ -709,48 +701,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp return true; } - /** - * Helper class that allows us to retrieve the method handle for this function once it has been generated. - */ - private static class MethodLocator implements Serializable { - private transient Class clazz; - private final String className; - private final String methodName; - private final MethodType methodType; - private final int functionFlags; - - private static final long serialVersionUID = -5420835725902966692L; - - MethodLocator(final FunctionNode functionNode) { - this.className = functionNode.getCompileUnit().getUnitClassName(); - this.methodName = functionNode.getName(); - this.methodType = new FunctionSignature(functionNode).getMethodType(); - this.functionFlags = functionNode.getFlags(); - - assert className != null; - assert methodName != null; - } - - void setClass(final Class clazz) { - if (!JS.class.isAssignableFrom(clazz)) { - throw new IllegalArgumentException(); - } - this.clazz = clazz; - } - - String getClassName() { - return className; - } - - MethodHandle getMethodHandle() { - return MH.findStatic(LOOKUP, clazz, methodName, methodType); - } - - int getFunctionFlags() { - return functionFlags; - } - } - private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); createLogger(); diff --git a/nashorn/test/script/basic/JDK-8048079_1.js b/nashorn/test/script/basic/JDK-8048079_1.js new file mode 100644 index 00000000000..fcd21219e02 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048079_1.js @@ -0,0 +1,35 @@ +/* + * 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-8048079: Persistent code store is broken after optimistic types merge + * + * @test + * @run + * @option -pcc + * @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache + * @fork + */ + +load(__DIR__ + 'prototype.js'); +load(__DIR__ + 'yui.js'); diff --git a/nashorn/test/script/basic/JDK-8048079_1.js.EXPECTED b/nashorn/test/script/basic/JDK-8048079_1.js.EXPECTED new file mode 100644 index 00000000000..371f63a54d5 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048079_1.js.EXPECTED @@ -0,0 +1,3 @@ +parsed and compiled ok prototype.js +parsed and compiled ok yui-min.js +parsed and compiled ok yui.js diff --git a/nashorn/test/script/basic/JDK-8048079_2.js b/nashorn/test/script/basic/JDK-8048079_2.js new file mode 100644 index 00000000000..fcd21219e02 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048079_2.js @@ -0,0 +1,35 @@ +/* + * 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-8048079: Persistent code store is broken after optimistic types merge + * + * @test + * @run + * @option -pcc + * @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache + * @fork + */ + +load(__DIR__ + 'prototype.js'); +load(__DIR__ + 'yui.js'); diff --git a/nashorn/test/script/basic/JDK-8048079_2.js.EXPECTED b/nashorn/test/script/basic/JDK-8048079_2.js.EXPECTED new file mode 100644 index 00000000000..371f63a54d5 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048079_2.js.EXPECTED @@ -0,0 +1,3 @@ +parsed and compiled ok prototype.js +parsed and compiled ok yui-min.js +parsed and compiled ok yui.js diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/CodeStoreAndPathTest.java b/nashorn/test/src/jdk/nashorn/internal/runtime/CodeStoreAndPathTest.java index f6b72d3cd15..b7999dfc4de 100644 --- a/nashorn/test/src/jdk/nashorn/internal/runtime/CodeStoreAndPathTest.java +++ b/nashorn/test/src/jdk/nashorn/internal/runtime/CodeStoreAndPathTest.java @@ -96,7 +96,7 @@ public class CodeStoreAndPathTest { final String codeCache = "build/nashorn_code_cache"; final String oldUserDir = System.getProperty("user.dir"); - private static final String[] ENGINE_OPTIONS = new String[]{"--persistent-code-cache", "--optimistic-types=false", "--lazy-compilation=false"}; + private static final String[] ENGINE_OPTIONS = new String[]{"--persistent-code-cache"}; public void checkCompiledScripts(final DirectoryStream stream, int numberOfScripts) throws IOException { for (final Path file : stream) { From 9dde0bfa35258bbc04ad2a3f81d4a6259fb41582 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Thu, 26 Jun 2014 13:12:32 +0200 Subject: [PATCH 11/17] 8047357: More precise synthetic return + unreachable throw Reviewed-by: lagergren, sundar --- .../internal/codegen/AssignSymbols.java | 8 ++-- .../internal/codegen/CodeGenerator.java | 2 +- .../codegen/LocalVariableTypesCalculator.java | 46 ++++++++++++++++++- .../jdk/nashorn/internal/codegen/Lower.java | 25 ---------- .../src/jdk/nashorn/internal/ir/Block.java | 7 ++- .../src/jdk/nashorn/internal/ir/CaseNode.java | 7 ++- .../internal/ir/ExpressionStatement.java | 5 -- .../jdk/nashorn/internal/ir/IdentNode.java | 4 +- nashorn/src/jdk/nashorn/internal/ir/Node.java | 28 ----------- .../jdk/nashorn/internal/ir/Statement.java | 30 +++++++++++- .../src/jdk/nashorn/internal/ir/Terminal.java | 37 +++++++++++++++ .../nashorn/internal/ir/debug/ASTWriter.java | 6 ++- .../internal/ir/debug/PrintVisitor.java | 10 ++-- nashorn/test/script/basic/JDK-8047057.js | 4 +- nashorn/test/script/basic/JDK-8047357.js | 32 +++++++++++++ .../test/script/basic/JDK-8047357.js.EXPECTED | 2 + 16 files changed, 173 insertions(+), 80 deletions(-) create mode 100644 nashorn/src/jdk/nashorn/internal/ir/Terminal.java create mode 100644 nashorn/test/script/basic/JDK-8047357.js create mode 100644 nashorn/test/script/basic/JDK-8047357.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java index 65e65c785a1..0659e3f5c3e 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -209,7 +209,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements if (varNode.isFunctionDeclaration()) { symbol.setIsFunctionDeclaration(); } - return varNode.setName((IdentNode)ident.setSymbol(symbol)); + return varNode.setName(ident.setSymbol(symbol)); } return varNode; } @@ -217,7 +217,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements } private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { - return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); + return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); } /** @@ -263,7 +263,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); assert nameSymbol != null; - return (VarNode)synthVar.setName((IdentNode)name.setSymbol(nameSymbol)).accept(this); + return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); } private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { @@ -522,7 +522,7 @@ final class AssignSymbols extends NodeOperatorVisitor implements final Symbol paramSymbol = body.getExistingSymbol(param.getName()); assert paramSymbol != null; assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); - newParams.add((IdentNode)param.setSymbol(paramSymbol)); + newParams.add(param.setSymbol(paramSymbol)); // parameters should not be slots for a function that uses variable arity signature if (isVarArg) { diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java index de971ab0308..bfb9851c0f3 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -1097,7 +1097,7 @@ final class CodeGenerator extends NodeOperatorVisitor{ private boolean reachable = true; // Return type of the function private Type returnType = Type.UNKNOWN; + // Synthetic return node that we must insert at the end of the function if it's end is reachable. + private ReturnNode syntheticReturn; // Topmost current split node (if any) private SplitNode topSplit; @@ -845,6 +849,10 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ @Override public boolean enterThrowNode(final ThrowNode throwNode) { + if(!reachable) { + return false; + } + throwNode.getExpression().accept(this); jumpToCatchBlock(throwNode); doesNotContinueSequentially(); @@ -1031,6 +1039,15 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ @Override public Node leaveBlock(final Block block) { if(lc.isFunctionBody()) { + if(reachable) { + // reachable==true means we can reach the end of the function without an explicit return statement. We + // need to insert a synthetic one then. This logic used to be in Lower.leaveBlock(), but Lower's + // reachability analysis (through Terminal.isTerminal() flags) is not precise enough so + // Lower$BlockLexicalContext.afterSetStatements will sometimes think the control flow terminates even + // when it didn't. Example: function() { switch((z)) { default: {break; } throw x; } }. + createSyntheticReturn(block); + assert !reachable; + } // We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of // the :return symbol and thus affect conversion type liveness calculations for it. calculateReturnType(); @@ -1089,6 +1106,23 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ retSymbol.setNeedsSlot(true); } } + + private void createSyntheticReturn(final Block body) { + final FunctionNode functionNode = lc.getCurrentFunction(); + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + final List statements = body.getStatements(); + final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber(); + final IdentNode returnExpr; + if(functionNode.isProgram()) { + returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN)); + } else { + returnExpr = null; + } + syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr); + syntheticReturn.accept(this); + } + /** * Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one * break statement to the end of the node), insert the join point into the flow. @@ -1177,6 +1211,16 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ return node; } + @Override + public Node leaveBlock(final Block block) { + if(inOuterFunction && syntheticReturn != null && lc.isFunctionBody()) { + final ArrayList stmts = new ArrayList<>(block.getStatements()); + stmts.add((ReturnNode)syntheticReturn.accept(this)); + return block.setStatements(lc, stmts); + } + return super.leaveBlock(block); + } + @Override public Node leaveFunctionNode(final FunctionNode nestedFunctionNode) { inOuterFunction = true; diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java index 3da0f778712..352270d05dd 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java @@ -75,7 +75,6 @@ import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.CodeInstaller; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; @@ -159,30 +158,6 @@ final class Lower extends NodeOperatorVisitor implements Lo return context.getLogger(this.getClass()); } - @Override - public Node leaveBlock(final Block block) { - //now we have committed the entire statement list to the block, but we need to truncate - //whatever is after the last terminal. block append won't append past it - - - if (lc.isFunctionBody()) { - final FunctionNode currentFunction = lc.getCurrentFunction(); - final boolean isProgram = currentFunction.isProgram(); - final Statement last = lc.getLastStatement(); - final ReturnNode returnNode = new ReturnNode( - last == null ? currentFunction.getLineNumber() : last.getLineNumber(), //TODO? - currentFunction.getToken(), - currentFunction.getFinish(), - isProgram ? - compilerConstant(RETURN) : - LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)); - - returnNode.accept(this); - } - - return block; - } - @Override public boolean enterBreakNode(final BreakNode breakNode) { addStatement(breakNode); diff --git a/nashorn/src/jdk/nashorn/internal/ir/Block.java b/nashorn/src/jdk/nashorn/internal/ir/Block.java index d8a84b6ef47..9d4356db6e7 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/Block.java +++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java @@ -41,7 +41,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor; * IR representation for a list of statements. */ @Immutable -public class Block extends Node implements BreakableNode, Flags { +public class Block extends Node implements BreakableNode, Terminal, Flags { /** List of statements */ protected final List statements; @@ -231,6 +231,11 @@ public class Block extends Node implements BreakableNode, Flags { return flags; } + /** + * Is this a terminal block, i.e. does it end control flow like ending with a throw or return? + * + * @return true if this node statement is terminal + */ @Override public boolean isTerminal() { return getFlag(IS_TERMINAL); diff --git a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java index 1c4cf3279c1..aae6d71f03d 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java +++ b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java @@ -36,7 +36,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor; * Case nodes are not BreakableNodes, but the SwitchNode is */ @Immutable -public final class CaseNode extends Node implements JoinPredecessor, Labels { +public final class CaseNode extends Node implements JoinPredecessor, Labels, Terminal { /** Test expression. */ private final Expression test; @@ -77,6 +77,11 @@ public final class CaseNode extends Node implements JoinPredecessor, Labels { this.conversion = conversion; } + /** + * Is this a terminal case node, i.e. does it end control flow like having a throw or return? + * + * @return true if this node statement is terminal + */ @Override public boolean isTerminal() { return body.isTerminal(); diff --git a/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java b/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java index e4e15788f7c..45870ff0920 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java +++ b/nashorn/src/jdk/nashorn/internal/ir/ExpressionStatement.java @@ -56,11 +56,6 @@ public final class ExpressionStatement extends Statement { this.expression = expression; } - @Override - public boolean isTerminal() { - return expression.isTerminal(); - } - @Override public Node accept(final NodeVisitor visitor) { if (visitor.enterExpressionStatement(this)) { diff --git a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java index 652c5860f72..3d4d7348979 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java +++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java @@ -110,7 +110,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function * @return a temporary identifier for the symbol. */ public static IdentNode createInternalIdentifier(final Symbol symbol) { - return (IdentNode)new IdentNode(Token.toDesc(TokenType.IDENT, 0, 0), 0, symbol.getName()).setSymbol(symbol); + return new IdentNode(Token.toDesc(TokenType.IDENT, 0, 0), 0, symbol.getName()).setSymbol(symbol); } @Override @@ -180,7 +180,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function * @param symbol the symbol * @return new node */ - public Expression setSymbol(final Symbol symbol) { + public IdentNode setSymbol(final Symbol symbol) { if (this.symbol == symbol) { return this; } diff --git a/nashorn/src/jdk/nashorn/internal/ir/Node.java b/nashorn/src/jdk/nashorn/internal/ir/Node.java index 4b290c3c2dd..191d702ab64 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/Node.java +++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java @@ -143,15 +143,6 @@ public abstract class Node implements Cloneable { */ public abstract void toString(final StringBuilder sb, final boolean printType); - /** - * Check if this node has terminal flags, i.e. ends or breaks control flow - * - * @return true if terminal - */ - public boolean hasTerminalFlags() { - return isTerminal() || hasGoto(); - } - /** * Get the finish position for this node in the source string * @return finish @@ -168,15 +159,6 @@ public abstract class Node implements Cloneable { this.finish = finish; } - /** - * Check if this function repositions control flow with goto like - * semantics, for example {@link BreakNode} or a {@link ForNode} with no test - * @return true if node has goto semantics - */ - public boolean hasGoto() { - return false; - } - /** * Get start position for node * @return start position @@ -249,16 +231,6 @@ public abstract class Node implements Cloneable { return token; } - /** - * Is this a terminal Node, i.e. does it end control flow like a throw or return - * expression does? - * - * @return true if this node is terminal - */ - public boolean isTerminal() { - return false; - } - //on change, we have to replace the entire list, that's we can't simple do ListIterator.set static List accept(final NodeVisitor visitor, final Class clazz, final List list) { boolean changed = false; diff --git a/nashorn/src/jdk/nashorn/internal/ir/Statement.java b/nashorn/src/jdk/nashorn/internal/ir/Statement.java index 6b171cb9597..b436f71fb0a 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/Statement.java +++ b/nashorn/src/jdk/nashorn/internal/ir/Statement.java @@ -30,7 +30,7 @@ package jdk.nashorn.internal.ir; * made up of statements. The only node subclass that needs to keep token and * location information is the Statement */ -public abstract class Statement extends Node { +public abstract class Statement extends Node implements Terminal { private final int lineNumber; @@ -77,4 +77,32 @@ public abstract class Statement extends Node { return lineNumber; } + /** + * Is this a terminal statement, i.e. does it end control flow like a throw or return? + * + * @return true if this node statement is terminal + */ + @Override + public boolean isTerminal() { + return false; + } + + /** + * Check if this statement repositions control flow with goto like + * semantics, for example {@link BreakNode} or a {@link ForNode} with no test + * @return true if statement has goto semantics + */ + public boolean hasGoto() { + return false; + } + + /** + * Check if this statement has terminal flags, i.e. ends or breaks control flow + * + * @return true if has terminal flags + */ + public final boolean hasTerminalFlags() { + return isTerminal() || hasGoto(); + } } + diff --git a/nashorn/src/jdk/nashorn/internal/ir/Terminal.java b/nashorn/src/jdk/nashorn/internal/ir/Terminal.java new file mode 100644 index 00000000000..3f5c25bc0f6 --- /dev/null +++ b/nashorn/src/jdk/nashorn/internal/ir/Terminal.java @@ -0,0 +1,37 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.nashorn.internal.ir; + +/** + * Interface for AST nodes that can have a flag determining if they can terminate function control flow. + */ +public interface Terminal { + /** + * Returns true if this AST node is (or contains) a statement that terminates function control flow. + * @return true if this AST node is (or contains) a statement that terminates function control flow. + */ + public boolean isTerminal(); +} diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java index 87f3c6c50dd..52e3c0e025f 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java @@ -38,7 +38,9 @@ import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.Terminal; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.annotations.Ignore; import jdk.nashorn.internal.ir.annotations.Reference; @@ -144,11 +146,11 @@ public final class ASTWriter { String status = ""; - if (node.isTerminal()) { + if (node instanceof Terminal && ((Terminal)node).isTerminal()) { status += " Terminal"; } - if (node.hasGoto()) { + if (node instanceof Statement && ((Statement)node).hasGoto()) { status += " Goto "; } diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java index df903513b9c..52e9b66311e 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java @@ -182,9 +182,9 @@ public final class PrintVisitor extends NodeVisitor { final List statements = block.getStatements(); - for (final Node statement : statements) { - if (printLineNumbers && (statement instanceof Statement)) { - final int lineNumber = ((Statement)statement).getLineNumber(); + for (final Statement statement : statements) { + if (printLineNumbers) { + final int lineNumber = statement.getLineNumber(); sb.append('\n'); if (lineNumber != lastLineNumber) { indent(); @@ -196,10 +196,6 @@ public final class PrintVisitor extends NodeVisitor { statement.accept(this); - if (statement instanceof FunctionNode) { - continue; - } - int lastIndex = sb.length() - 1; char lastChar = sb.charAt(lastIndex); while (Character.isWhitespace(lastChar) && lastIndex >= 0) { diff --git a/nashorn/test/script/basic/JDK-8047057.js b/nashorn/test/script/basic/JDK-8047057.js index 84183f19059..f13e36e4941 100644 --- a/nashorn/test/script/basic/JDK-8047057.js +++ b/nashorn/test/script/basic/JDK-8047057.js @@ -45,8 +45,8 @@ function makeFuncExpectError(code, ErrorType) { } } -// makeFuncAndCall("switch(0) { default: {break;} return }"); -// makeFuncAndCall("L: { { break L; } return; }"); +makeFuncAndCall("switch(0) { default: {break;} return }"); +makeFuncAndCall("L: { { break L; } return; }"); makeFuncAndCall("L: { while(0) break L; return; }"); makeFuncExpectError("L: {while(0) break L; return [](); }", TypeError); // makeFuncAndCall("do with({}) break ; while(0);"); diff --git a/nashorn/test/script/basic/JDK-8047357.js b/nashorn/test/script/basic/JDK-8047357.js new file mode 100644 index 00000000000..20e2bb4f020 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047357.js @@ -0,0 +1,32 @@ +/* + * 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-8047357: More precise synthetic return + unreachable throw + * + * @test + * @run + */ + +print((function() { switch(0) { default: {var x; break ; } throw x; } })()); +print((function() { switch(0) { default: {break;} return; } })()); diff --git a/nashorn/test/script/basic/JDK-8047357.js.EXPECTED b/nashorn/test/script/basic/JDK-8047357.js.EXPECTED new file mode 100644 index 00000000000..d4f80bf8362 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047357.js.EXPECTED @@ -0,0 +1,2 @@ +undefined +undefined From 3168df50f587c1ff71942d7e8c08cc83250bfbfc Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Thu, 26 Jun 2014 15:36:15 +0200 Subject: [PATCH 12/17] 8047371: local variable declaration in TypeEvaluator should use ScriptObject.addOwnProperty instead of .set Reviewed-by: jlaskey, lagergren --- .../internal/codegen/TypeEvaluator.java | 10 ++++-- nashorn/test/script/basic/JDK-8047371.js | 32 +++++++++++++++++++ .../test/script/basic/JDK-8047371.js.EXPECTED | 1 + 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8047371.js create mode 100644 nashorn/test/script/basic/JDK-8047371.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java b/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java index 97dbb90c789..2c90a3eb846 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/TypeEvaluator.java @@ -25,6 +25,10 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.runtime.Property.NOT_CONFIGURABLE; +import static jdk.nashorn.internal.runtime.Property.NOT_ENUMERABLE; +import static jdk.nashorn.internal.runtime.Property.NOT_WRITABLE; + import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.Expression; @@ -43,8 +47,8 @@ import jdk.nashorn.internal.runtime.ScriptRuntime; * Used during recompilation. */ final class TypeEvaluator { - final Compiler compiler; - final ScriptObject runtimeScope; + private final Compiler compiler; + private final ScriptObject runtimeScope; TypeEvaluator(final Compiler compiler, final ScriptObject runtimeScope) { this.compiler = compiler; @@ -123,7 +127,7 @@ final class TypeEvaluator { " scope="+runtimeScope; if (runtimeScope.findProperty(symbolName, false) == null) { - runtimeScope.set(symbolName, ScriptRuntime.UNDEFINED, true); + runtimeScope.addOwnProperty(symbolName, NOT_WRITABLE | NOT_ENUMERABLE | NOT_CONFIGURABLE, ScriptRuntime.UNDEFINED); } } diff --git a/nashorn/test/script/basic/JDK-8047371.js b/nashorn/test/script/basic/JDK-8047371.js new file mode 100644 index 00000000000..fb380c7806f --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047371.js @@ -0,0 +1,32 @@ +/* + * 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-8047371: local variable declaration in TypeEvaluator should use ScriptObject.addOwnProperty instead of .set + * + * @test + * @run + */ + +print((function(){ var a=1; with({ get a() { return false } }) return a })()); + diff --git a/nashorn/test/script/basic/JDK-8047371.js.EXPECTED b/nashorn/test/script/basic/JDK-8047371.js.EXPECTED new file mode 100644 index 00000000000..c508d5366f7 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047371.js.EXPECTED @@ -0,0 +1 @@ +false From 8f69a3e4a8e708a2ac44e012ef3300ead4a4860a Mon Sep 17 00:00:00 2001 From: Marcus Lagergren Date: Fri, 27 Jun 2014 10:47:44 +0200 Subject: [PATCH 13/17] 8048505: ScriptingFunctions.readFully couldn't handle file names represented as ConsStrings Reviewed-by: sundar, hannesw --- .../internal/runtime/ScriptingFunctions.java | 4 +- nashorn/test/script/basic/JDK-8048505.js | 52 +++++++++++++++++++ .../test/script/basic/JDK-8048505.js.EXPECTED | 7 +++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8048505.js create mode 100644 nashorn/test/script/basic/JDK-8048505.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java index 78747acb64f..2113b79c22e 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java @@ -107,8 +107,8 @@ public final class ScriptingFunctions { if (file instanceof File) { f = (File)file; - } else if (file instanceof String) { - f = new java.io.File((String)file); + } else if (file instanceof String || file instanceof ConsString) { + f = new java.io.File(((CharSequence)file).toString()); } if (f == null || !f.isFile()) { diff --git a/nashorn/test/script/basic/JDK-8048505.js b/nashorn/test/script/basic/JDK-8048505.js new file mode 100644 index 00000000000..4ee8fe16bbd --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048505.js @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/** + * Read fully parameter test + * + * @test + * @option -scripting + * @run + */ + +var str = __FILE__; +var first = readFully(str); +print(typeof str); + +var str2 = __FILE__.substring(0,5); +var str3 = __FILE__.substring(5); +print(typeof str2); +print(typeof str3); + +var cons = str2 + str3; +print(typeof cons); + +var second = readFully(cons); + +var f = new java.io.File(str); +print(typeof f); +var third = readFully(f); + +print(first.length() == second.length()); +print(first.length() == third.length()); + diff --git a/nashorn/test/script/basic/JDK-8048505.js.EXPECTED b/nashorn/test/script/basic/JDK-8048505.js.EXPECTED new file mode 100644 index 00000000000..ed5daae86f7 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048505.js.EXPECTED @@ -0,0 +1,7 @@ +string +string +string +string +object +true +true From 4b428d5833b11be7a465635d30dd588b3dc906ce Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Fri, 27 Jun 2014 21:54:16 +0530 Subject: [PATCH 14/17] 8046013: TypeError: Cannot apply "with" to non script object Reviewed-by: lagergren, hannesw --- .../internal/objects/NativeObject.java | 26 +++++++++ .../internal/runtime/ScriptRuntime.java | 19 ++++++- .../nashorn/internal/runtime/WithObject.java | 19 ++++++- nashorn/test/script/basic/JDK-8046013.js | 57 +++++++++++++++++++ .../test/script/basic/JDK-8046013.js.EXPECTED | 4 ++ .../api/scripting/ScriptEngineTest.java | 14 +++++ 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8046013.js create mode 100644 nashorn/test/script/basic/JDK-8046013.js.EXPECTED diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java b/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java index 30ffbabff9e..08ad24cc8a7 100644 --- a/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java +++ b/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java @@ -716,6 +716,32 @@ public final class NativeObject { return target; } + /* + * Binds the source mirror object's properties to the target object. Binding + * properties allows two-way read/write for the properties of the source object. + * All inherited, enumerable properties are also bound. This method is used to + * to make 'with' statement work with ScriptObjectMirror as scope object. + * + * @param target the target object to which the source object's properties are bound + * @param source the source object whose properties are bound to the target + * @return the target object after property binding + */ + public static Object bindAllProperties(final ScriptObject target, final ScriptObjectMirror source) { + final Set keys = source.keySet(); + // make accessor properties using dynamic invoker getters and setters + final AccessorProperty[] props = new AccessorProperty[keys.size()]; + int idx = 0; + for (String name : keys) { + final MethodHandle getter = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getElem:" + name, MIRROR_GETTER_TYPE); + final MethodHandle setter = Bootstrap.createDynamicInvoker("dyn:setProp|setElem:" + name, MIRROR_SETTER_TYPE); + props[idx] = AccessorProperty.create(name, 0, getter, setter); + idx++; + } + + target.addBoundProperties(source, props); + return target; + } + private static void bindBeanProperties(final ScriptObject targetObj, final Object source, final Collection readablePropertyNames, final Collection writablePropertyNames, final Collection methodNames) { diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java index 580e95d80ea..337d92699e3 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java @@ -49,6 +49,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.ir.debug.JSONWriter; import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.objects.NativeObject; import jdk.nashorn.internal.parser.Lexer; import jdk.nashorn.internal.runtime.linker.Bootstrap; @@ -478,9 +479,21 @@ public final class ScriptRuntime { throw typeError(global, "cant.apply.with.to.null"); } - final Object wrappedExpr = JSType.toScriptObject(global, expression); - if (wrappedExpr instanceof ScriptObject) { - return new WithObject(scope, (ScriptObject)wrappedExpr); + if (expression instanceof ScriptObjectMirror) { + final Object unwrapped = ScriptObjectMirror.unwrap(expression, global); + if (unwrapped instanceof ScriptObject) { + return new WithObject(scope, (ScriptObject)unwrapped); + } else { + // foreign ScriptObjectMirror + ScriptObject exprObj = global.newObject(); + NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression); + return new WithObject(scope, exprObj); + } + } else { + final Object wrappedExpr = JSType.toScriptObject(global, expression); + if (wrappedExpr instanceof ScriptObject) { + return new WithObject(scope, (ScriptObject)wrappedExpr); + } } throw typeError(global, "cant.apply.with.to.non.scriptobject"); diff --git a/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java b/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java index 24c5e3d9ff8..ac5f5a76e4a 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java @@ -31,6 +31,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.SwitchPoint; +import jdk.nashorn.api.scripting.AbstractJSObject; +import jdk.nashorn.api.scripting.ScriptObjectMirror; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; @@ -312,7 +314,22 @@ public final class WithObject extends ScriptObject implements Scope { @SuppressWarnings("unused") private static Object bindToExpression(final Object fn, final Object receiver) { - return fn instanceof ScriptFunction ? bindToExpression((ScriptFunction) fn, receiver) : fn; + if (fn instanceof ScriptFunction) { + return bindToExpression((ScriptFunction) fn, receiver); + } else if (fn instanceof ScriptObjectMirror) { + final ScriptObjectMirror mirror = (ScriptObjectMirror)fn; + if (mirror.isFunction()) { + // We need to make sure correct 'this' is used for calls with Ident call + // expressions. We do so here using an AbstractJSObject instance. + return new AbstractJSObject() { + public Object call(final Object thiz, final Object... args) { + return mirror.call(withFilterExpression(receiver), args); + } + }; + } + } + + return fn; } private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { diff --git a/nashorn/test/script/basic/JDK-8046013.js b/nashorn/test/script/basic/JDK-8046013.js new file mode 100644 index 00000000000..e3a5ac4e9be --- /dev/null +++ b/nashorn/test/script/basic/JDK-8046013.js @@ -0,0 +1,57 @@ +/* + * 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-8046013: TypeError: Cannot apply "with" to non script object + * + * @test + * @run + */ + +var obj = loadWithNewGlobal({ + script: "({ f: 33 })", + name: "test" +}); + +with (obj) { + print("f = " + f); +} + +var obj2 = loadWithNewGlobal({ + script: "var obj = Object.create({ foo: 42 }); obj.bar = 'hello'; obj", + name: "test2" +}); + +with (obj2) { + print("foo = " + foo); + print("bar = " + bar); +} + +var obj3 = loadWithNewGlobal({ + script: "({ f: 33, func: function() { print('this.f =', this.f); } })", + name: "test" +}); + +with(obj3) { + func(); +} diff --git a/nashorn/test/script/basic/JDK-8046013.js.EXPECTED b/nashorn/test/script/basic/JDK-8046013.js.EXPECTED new file mode 100644 index 00000000000..4b612ed6693 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8046013.js.EXPECTED @@ -0,0 +1,4 @@ +f = 33 +foo = 42 +bar = hello +this.f = 33 diff --git a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java index 6989d6c646f..87d9b5ef35b 100644 --- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java +++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java @@ -593,6 +593,20 @@ public class ScriptEngineTest { } } + // @bug 8046013: TypeError: Cannot apply "with" to non script object + @Test + public void withOnMirrorTest() throws ScriptException { + final ScriptEngineManager m = new ScriptEngineManager(); + final ScriptEngine e = m.getEngineByName("nashorn"); + + final Object obj = e.eval("({ foo: 'hello'})"); + final Object[] arr = new Object[1]; + arr[0] = obj; + e.put("arr", arr); + final Object res = e.eval("var res; with(arr[0]) { res = foo; }; res"); + assertEquals(res, "hello"); + } + private static void checkProperty(final ScriptEngine e, final String name) throws ScriptException { final String value = System.getProperty(name); From 51aad86e6b4d0c6b2f432257edb7ac89f03d9e1d Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Mon, 30 Jun 2014 20:23:16 +0530 Subject: [PATCH 15/17] 8048718: JSON.parse('{"0":0, "64":0}') throws ArrayindexOutOfBoundsException Reviewed-by: lagergren, hannesw, attila --- .../internal/runtime/ScriptObject.java | 1 + nashorn/test/script/basic/JDK-8048718.js | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 nashorn/test/script/basic/JDK-8048718.js diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java index 90003b38f67..1987d317100 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java @@ -691,6 +691,7 @@ public abstract class ScriptObject implements PropertyAccess { assert isValidArrayIndex(index) : "invalid array index"; final long longIndex = ArrayIndex.toLongIndex(index); doesNotHaveEnsureDelete(longIndex, getArray().length(), false); + setArray(getArray().ensure(longIndex)); setArray(getArray().set(index, value, false)); } diff --git a/nashorn/test/script/basic/JDK-8048718.js b/nashorn/test/script/basic/JDK-8048718.js new file mode 100644 index 00000000000..08b0e3f8879 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048718.js @@ -0,0 +1,52 @@ +/* + * 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-8048718: JSON.parse('{"0":0, "64":0}') throws ArrayindexOutOfBoundsException + * + * @test + * @run + */ + +var obj = JSON.parse('{"0":0, "64":0}'); +if ("1" in obj) { + fail("found element at index 1"); +} + +if ("63" in obj) { + fail("found element at index 63"); +} + +if (obj[0] != 0) { + fail("expected obj[0] to be 0"); +} + +if (obj[64] != 0) { + fail("expected obj[64] to be 0"); +} + +for (var i in obj) { + if (i != "0" && i != "64") { + fail("invalid property " + i); + } +} From 3e0fd99223bc26c3784df881f46859b2da49477a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 30 Jun 2014 17:31:28 +0200 Subject: [PATCH 16/17] 8048586: String concatenation with optimistic types is slow Reviewed-by: lagergren, attila --- .../codegen/LocalVariableTypesCalculator.java | 4 +- .../internal/codegen/types/ObjectType.java | 2 + .../nashorn/internal/codegen/types/Type.java | 16 ++++++++ .../jdk/nashorn/internal/runtime/JSType.java | 13 ++++++ nashorn/test/script/basic/JDK-8048586.js | 41 +++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 nashorn/test/script/basic/JDK-8048586.js diff --git a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 9a6bcef259d..df763c1ba4f 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -1196,7 +1196,9 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ } else if(binaryNode.isOptimisticUndecidedType()) { // At this point, we can assign a static type to the optimistic binary ADD operator as now we know // the types of its operands. - return binaryNode.setType(Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType())); + final Type type = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); + // Use Type.CHARSEQUENCE instead of Type.STRING to avoid conversion of ConsStrings to Strings. + return binaryNode.setType(type.equals(Type.STRING) ? Type.CHARSEQUENCE : type); } return binaryNode; } diff --git a/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java b/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java index 50cc02fc9ea..0ccf6644a59 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/types/ObjectType.java @@ -171,6 +171,8 @@ class ObjectType extends Type { invokestatic(method, JSType.TO_BOOLEAN); } else if (to.isString()) { invokestatic(method, JSType.TO_PRIMITIVE_TO_STRING); + } else if (to.isCharSequence()) { + invokestatic(method, JSType.TO_PRIMITIVE_TO_CHARSEQUENCE); } else { throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to + " " + isString() + " " + toString); } diff --git a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java index 18b2c0d3a77..1fc648039ff 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java @@ -416,6 +416,15 @@ public abstract class Type implements Comparable, BytecodeOps { return this.equals(Type.STRING); } + /** + * Determines whether a type is a CHARSEQUENCE type used internally strings + * + * @return true if CharSequence (internal string) type, false otherwise + */ + public boolean isCharSequence() { + return this.equals(Type.CHARSEQUENCE); + } + /** * Determine if two types are equivalent, i.e. need no conversion * @@ -799,6 +808,13 @@ public abstract class Type implements Comparable, BytecodeOps { */ public static final Type STRING = putInCache(new ObjectType(String.class)); + /** + * This is the CharSequence singleton used to represent JS strings internally + * (either a {@code java.lang.String} or {@code jdk.nashorn.internal.runtime.ConsString}. + */ + public static final Type CHARSEQUENCE = putInCache(new ObjectType(CharSequence.class)); + + /** * This is the object singleton, used for all object types */ diff --git a/nashorn/src/jdk/nashorn/internal/runtime/JSType.java b/nashorn/src/jdk/nashorn/internal/runtime/JSType.java index 1d4e409fd3d..388761c4655 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/JSType.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/JSType.java @@ -130,6 +130,9 @@ public enum JSType { /** Combined call to toPrimitive followed by toString. */ public static final Call TO_PRIMITIVE_TO_STRING = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToString", String.class, Object.class); + /** Combined call to toPrimitive followed by toCharSequence. */ + public static final Call TO_PRIMITIVE_TO_CHARSEQUENCE = staticCall(JSTYPE_LOOKUP, JSType.class, "toPrimitiveToCharSequence", CharSequence.class, Object.class); + /** Throw an unwarranted optimism exception */ public static final Call THROW_UNWARRANTED = staticCall(JSTYPE_LOOKUP, JSType.class, "throwUnwarrantedOptimismException", Object.class, Object.class, int.class); @@ -487,6 +490,16 @@ public enum JSType { return toString(toPrimitive(obj)); } + /** + * Like {@link #toPrimitiveToString(Object)}, but avoids conversion of ConsString to String. + * + * @param obj an object + * @return the CharSequence form of the primitive form of the object + */ + public static CharSequence toPrimitiveToCharSequence(final Object obj) { + return toCharSequence(toPrimitive(obj)); + } + /** * JavaScript compliant conversion of number to boolean * diff --git a/nashorn/test/script/basic/JDK-8048586.js b/nashorn/test/script/basic/JDK-8048586.js new file mode 100644 index 00000000000..03941985877 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8048586.js @@ -0,0 +1,41 @@ +/* + * 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-8048586: String concatenation with optimistic types is slow + * + * @test + * @run + */ + +var body = ''; + +for (var i = 0; i < 1024 * 1024; i++) { + body += 'hello world\n'; +} + +body = ''; + +for (var i = 0; i < 1024 * 1024; i++) { + body = body + 'hello world\n'; +} From 81f8f932251d03b563869fa059405e0db9afe453 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Tue, 1 Jul 2014 17:37:39 +0530 Subject: [PATCH 17/17] 8047359: large string size RangeError should be thrown rather than reporting negative length Reviewed-by: hannesw, attila --- nashorn/make/build.xml | 3 +- .../dynalink/beans/OverloadedMethod.java | 2 +- ...mpositeTypeBasedGuardingDynamicLinker.java | 3 +- .../internal/codegen/FunctionSignature.java | 2 +- .../internal/ir/debug/NashornClassReader.java | 2 +- .../nashorn/internal/objects/NativeDate.java | 1 + .../nashorn/internal/runtime/ConsString.java | 3 ++ .../internal/runtime/ScriptRuntime.java | 7 ++- .../jdk/nashorn/internal/runtime/Source.java | 1 + .../runtime/UserAccessorProperty.java | 4 +- .../runtime/arrays/ByteBufferArrayData.java | 2 +- .../internal/runtime/linker/InvokeByName.java | 2 +- .../runtime/resources/Messages.properties | 1 + nashorn/test/script/basic/JDK-8047359.js | 47 +++++++++++++++++++ 14 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 nashorn/test/script/basic/JDK-8047359.js diff --git a/nashorn/make/build.xml b/nashorn/make/build.xml index 1b28e9bdcbe..a872f358e6d 100644 --- a/nashorn/make/build.xml +++ b/nashorn/make/build.xml @@ -125,8 +125,7 @@ encoding="${javac.encoding}" includeantruntime="false" fork="true"> - - + diff --git a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java index 75332859f17..70ec495a7ac 100644 --- a/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java +++ b/nashorn/src/jdk/internal/dynalink/beans/OverloadedMethod.java @@ -152,7 +152,7 @@ class OverloadedMethod { @SuppressWarnings("unused") private MethodHandle selectMethod(final Object[] args) throws NoSuchMethodException { - final Class[] argTypes = new Class[args.length]; + final Class[] argTypes = new Class[args.length]; for(int i = 0; i < argTypes.length; ++i) { final Object arg = args[i]; argTypes[i] = arg == null ? ClassString.NULL_CLASS : arg.getClass(); diff --git a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java index 814fc6936bf..91946969370 100644 --- a/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java +++ b/nashorn/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java @@ -111,7 +111,7 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin private final TypeBasedGuardingDynamicLinker[] linkers; private final List[] singletonLinkers; - @SuppressWarnings("unchecked") + @SuppressWarnings(value={"unchecked", "rawtypes"}) ClassToLinker(final TypeBasedGuardingDynamicLinker[] linkers) { this.linkers = linkers; singletonLinkers = new List[linkers.length]; @@ -120,6 +120,7 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin } } + @SuppressWarnings("fallthrough") @Override protected List computeValue(final Class clazz) { List list = NO_LINKER; diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java index 627623b378e..5e06794609c 100644 --- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java +++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java @@ -141,7 +141,7 @@ public final class FunctionSignature { paramTypeList.add(paramType.getTypeClass()); } - this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length])); + this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length])); } /** diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java b/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java index 3fd2ce96750..87eb18df73c 100644 --- a/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java +++ b/nashorn/src/jdk/nashorn/internal/ir/debug/NashornClassReader.java @@ -443,7 +443,7 @@ public class NashornClassReader extends ClassReader { @Override protected Label readLabel(final int offset, final Label[] labels) { final Label label = super.readLabel(offset, labels); - label.info = (int)offset; + label.info = offset; return label; } diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java index b3661df7eb2..0f42998d261 100644 --- a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java +++ b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java @@ -909,6 +909,7 @@ public final class NativeDate extends ScriptObject { sb.append(n); } + @SuppressWarnings("fallthrough") private static String toStringImpl(final Object self, final int format) { final NativeDate nd = getNativeDate(self); diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java b/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java index 41e2c49e938..3d1979d6c62 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ConsString.java @@ -57,6 +57,9 @@ public final class ConsString implements CharSequence { this.left = left; this.right = right; length = left.length() + right.length(); + if (length < 0) { + throw new IllegalArgumentException("too big concatenated String"); + } } @Override diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java index 337d92699e3..a73b7ab992f 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java @@ -27,6 +27,7 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; @@ -531,7 +532,11 @@ public final class ScriptRuntime { if (xPrim instanceof String || yPrim instanceof String || xPrim instanceof ConsString || yPrim instanceof ConsString) { - return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); + try { + return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); + } catch (final IllegalArgumentException iae) { + throw rangeError(iae, "concat.string.too.big"); + } } return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Source.java b/nashorn/src/jdk/nashorn/internal/runtime/Source.java index d64bbfa26e6..b5829eee735 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/Source.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/Source.java @@ -306,6 +306,7 @@ public final class Source implements Loggable { return array == null; } + @SuppressWarnings("try") protected void checkPermissionAndClose() throws IOException { try (InputStream in = url.openStream()) { // empty diff --git a/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java b/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java index 03a17f7bc0c..0a29d8911a6 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java @@ -201,7 +201,7 @@ public final class UserAccessorProperty extends SpillProperty { @Override public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { - return userAccessorGetter(getAccessors((owner != null) ? owner : (ScriptObject)self), self); + return userAccessorGetter(getAccessors((owner != null) ? owner : self), self); } @Override @@ -221,7 +221,7 @@ public final class UserAccessorProperty extends SpillProperty { @Override public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { - userAccessorSetter(getAccessors((owner != null) ? owner : (ScriptObject)self), strict ? getKey() : null, self, value); + userAccessorSetter(getAccessors((owner != null) ? owner : self), strict ? getKey() : null, self, value); } @Override diff --git a/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java b/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java index b76d0756ea5..85f1b433383 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/arrays/ByteBufferArrayData.java @@ -150,7 +150,7 @@ final class ByteBufferArrayData extends ArrayData { @Override public Object getObject(final int index) { - return (int)(0x0ff & buf.get(index)); + return 0x0ff & buf.get(index); } @Override diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java index e6d13c20c9a..19fe6bd758c 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java +++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/InvokeByName.java @@ -90,7 +90,7 @@ public final class InvokeByName { if(plength == 0) { finalPtypes = new Class[] { Object.class, targetClass }; } else { - finalPtypes = new Class[plength + 2]; + finalPtypes = new Class[plength + 2]; finalPtypes[0] = Object.class; finalPtypes[1] = targetClass; System.arraycopy(ptypes, 0, finalPtypes, 2, plength); diff --git a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties index 29785a3f70e..53cb4a68290 100644 --- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties +++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties @@ -151,6 +151,7 @@ range.error.invalid.precision=precision argument toPrecision() must be in [1, 21 range.error.invalid.radix=radix argument must be in [2, 36] range.error.invalid.date=Invalid Date range.error.too.many.errors=Script contains too many errors: {0} errors +range.error.concat.string.too.big=Concatenated String is too big reference.error.not.defined="{0}" is not defined reference.error.cant.be.used.as.lhs="{0}" can not be used as the left-hand side of assignment diff --git a/nashorn/test/script/basic/JDK-8047359.js b/nashorn/test/script/basic/JDK-8047359.js new file mode 100644 index 00000000000..812f3414347 --- /dev/null +++ b/nashorn/test/script/basic/JDK-8047359.js @@ -0,0 +1,47 @@ +/* + * 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-8047359: large string size RangeError should be thrown rather than reporting negative length + * + * @test + * @run + */ + +try { + var s = " "; for (var i=0;i<31;++i) s+=s; s.length; + throw new Error("should have thrown RangeError!"); +} catch (e) { + if (! (e instanceof RangeError)) { + fail("RangeError expected, got " + e); + } +} + +try { + var s = " "; for (var i=0;i<31;++i) s+=s; + throw new Error("should have thrown RangeError!"); +} catch (e) { + if (! (e instanceof RangeError)) { + fail("RangeError expected, got " + e); + } +}