8047959: bindings created for declarations in eval code are not mutable
authorsundar
Tue, 24 Jun 2014 19:43:44 +0530
changeset 25240 f92c14b1ca11
parent 25239 7cb49fc90bab
child 25241 92eb440faf5c
8047959: bindings created for declarations in eval code are not mutable Reviewed-by: jlaskey, attila
nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java
nashorn/src/jdk/nashorn/internal/objects/Global.java
nashorn/src/jdk/nashorn/internal/runtime/Context.java
nashorn/src/jdk/nashorn/internal/runtime/Source.java
nashorn/test/script/basic/JDK-8047959.js
nashorn/test/script/basic/JDK-8047959.js.EXPECTED
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Tue Jun 24 19:43:44 2014 +0530
@@ -211,6 +211,9 @@
      *  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 @@
     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());
@@ -291,6 +295,14 @@
     }
 
     /**
+     * Are we generating code for 'eval' code?
+     * @return true if currently compiled code is 'eval' code.
+     */
+    boolean isEvalCode() {
+        return evalCode;
+    }
+
+    /**
      * Load an identity node
      *
      * @param identNode an identity node to load
--- a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java	Tue Jun 24 19:43:44 2014 +0530
@@ -62,6 +62,8 @@
 
     /** 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 @@
     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 @@
     @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;
     }
 
--- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Tue Jun 24 19:43:44 2014 +0530
@@ -584,7 +584,7 @@
         }
         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()) {
--- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java	Tue Jun 24 19:43:44 2014 +0530
@@ -63,13 +63,13 @@
     /**
      * 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 @@
             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 @@
 
             //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 @@
      *
      * @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 @@
             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;
         }
 
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java	Tue Jun 24 19:43:44 2014 +0530
@@ -883,7 +883,7 @@
         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);
     }
 
     /**
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java	Tue Jun 24 19:43:44 2014 +0530
@@ -560,12 +560,29 @@
      * @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;
--- a/nashorn/src/jdk/nashorn/internal/runtime/Source.java	Mon Jun 23 18:32:11 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Source.java	Tue Jun 24 19:43:44 2014 +0530
@@ -142,29 +142,34 @@
         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 @@
                 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 @@
         }
 
 
+        @Override
+        public boolean isEvalCode() {
+            return evalCode;
+        }
     }
 
     private static class URLData implements Data {
@@ -287,6 +297,11 @@
             return array;
         }
 
+        @Override
+        public boolean isEvalCode() {
+            return false;
+        }
+
         boolean isDeferred() {
             return array == null;
         }
@@ -373,11 +388,23 @@
      *
      * @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
+     *
+     * @param name    source name
+     * @param content contents as char array
      *
      * @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 @@
      *
      * @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);
     }
 
     /**
@@ -555,6 +593,15 @@
     }
 
     /**
+     * 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.
      * @return Index of first character of line.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8047959.js	Tue Jun 24 19:43:44 2014 +0530
@@ -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); 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8047959.js.EXPECTED	Tue Jun 24 19:43:44 2014 +0530
@@ -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