8047078: Fuzzing bug discovered when ArrayLiteralNodes weren't immutable

Reviewed-by: attila, sundar
This commit is contained in:
Marcus Lagergren 2014-06-19 10:46:31 +02:00
parent 5ba4319c94
commit 18ac28e76f
7 changed files with 277 additions and 171 deletions

View File

@ -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<LexicalContext>(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;
}

View File

@ -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<LexicalContext>{
@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

View File

@ -307,7 +307,7 @@ final class Splitter extends NodeVisitor<LexicalContext> {
units.add(new ArrayUnit(unit, lo, postsets.length));
}
arrayLiteralNode.setUnits(units);
return arrayLiteralNode.setUnits(lc, units);
}
return literal;

View File

@ -173,7 +173,6 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
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;

View File

@ -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<T> 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<T> extends Expression implements PropertyKey {
/**
* Array literal node class.
*/
@Immutable
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> 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<ArrayUnit> units;
/** Sub units with indexes ranges, in which to split up code generation, for large literals */
private final List<ArrayUnit> 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<T> 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<T> 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<ArrayUnit> 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<T> 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<T> 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<T> 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<T> 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<ArrayUnit> units) {
this.units = units;
public ArrayLiteralNode setUnits(final LexicalContext lc, final List<ArrayUnit> 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<T> 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<Expression> 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

View File

@ -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/])');

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* JDK-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{[]}");