8047959: bindings created for declarations in eval code are not mutable

Reviewed-by: jlaskey, attila
This commit is contained in:
Athijegannathan Sundararajan 2014-06-24 19:43:44 +05:30
parent 3ad3dd1d89
commit 566786aba6
9 changed files with 179 additions and 21 deletions

View File

@ -211,6 +211,9 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
* by reflection in class installation */
private final Compiler compiler;
/** Is the current code submitted by 'eval' call? */
private final boolean evalCode;
/** Call site flags given to the code generator to be used for all generated call sites */
private final int callSiteFlags;
@ -265,6 +268,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) {
super(new CodeGeneratorLexicalContext());
this.compiler = compiler;
this.evalCode = compiler.getSource().isEvalCode();
this.continuationEntryPoints = continuationEntryPoints;
this.callSiteFlags = compiler.getScriptEnvironment()._callsite_flags;
this.log = initLogger(compiler.getContext());
@ -290,6 +294,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return lc.getCurrentFunction().isStrict() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags;
}
/**
* Are we generating code for 'eval' code?
* @return true if currently compiled code is 'eval' code.
*/
boolean isEvalCode() {
return evalCode;
}
/**
* Load an identity node
*

View File

@ -62,6 +62,8 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator<T> {
/** 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<T> extends ObjectCreator<T> {
FieldObjectCreator(final CodeGenerator codegen, final List<MapTuple<T>> 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<T> extends ObjectCreator<T> {
@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;
}

View File

@ -584,7 +584,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
}
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()) {

View File

@ -63,13 +63,13 @@ public class MapCreator<T> {
/**
* 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<Property> properties = new ArrayList<>();
assert tuples != null;
@ -79,7 +79,7 @@ public class MapCreator<T> {
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<T> {
//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<T> {
*
* @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<T> {
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;
}

View File

@ -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);
}
/**

View File

@ -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 ? "<eval>" : 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;

View File

@ -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.

View File

@ -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);

View File

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