8029667: Prototype linking is incorrect
authorhannesw
Tue, 07 Jan 2014 14:16:23 +0100
changeset 22377 d0c9a671c1fe
parent 22376 841a5e2ffd3b
child 22378 ab34b295a702
8029667: Prototype linking is incorrect Reviewed-by: jlaskey, sundar
nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
nashorn/src/jdk/nashorn/internal/runtime/SetMethodCreator.java
nashorn/test/script/basic/JDK-8029667.js
nashorn/test/script/basic/JDK-8029667.js.EXPECTED
--- a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java	Tue Jan 07 18:14:18 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java	Tue Jan 07 14:16:23 2014 +0100
@@ -112,7 +112,7 @@
         return property != null && property.hasGetterFunction(prototype) ? self : prototype;
     }
 
-   /**
+    /**
      * Return the appropriate receiver for a setter.
      * @return appropriate receiver
      */
@@ -172,5 +172,20 @@
         property.setObjectValue(getSetterReceiver(), getOwner(), value, strict);
     }
 
+    /**
+     * Get the number of objects in the prototype chain between the {@code self} and the
+     * {@code owner} objects.
+     * @return the prototype chain length
+     */
+    int getProtoChainLength() {
+        assert self != null;
+        int length = 0;
+        for (ScriptObject obj = self; obj != prototype; obj = obj.getProto()) {
+            assert !(obj instanceof WithObject);
+            ++length;
+        }
+        return length;
+    }
+
 }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java	Tue Jan 07 18:14:18 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java	Tue Jan 07 14:16:23 2014 +0100
@@ -143,6 +143,8 @@
     private static final MethodHandle TRUNCATINGFILTER   = findOwnMH("truncatingFilter", Object[].class, int.class, Object[].class);
     private static final MethodHandle KNOWNFUNCPROPGUARD = findOwnMH("knownFunctionPropertyGuard", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, Object.class, ScriptFunction.class);
 
+    private static final ArrayList<MethodHandle> protoFilters = new ArrayList<>();
+
     /** Method handle for getting a function argument at a given index. Used from MapCreator */
     public static final Call GET_ARGUMENT       = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArgument", Object.class, int.class);
 
@@ -1712,6 +1714,44 @@
     }
 
     /**
+     * Test whether this object contains in its prototype chain or is itself a with-object.
+     * @return true if a with-object was found
+     */
+    final boolean hasWithScope() {
+        if (isScope()) {
+            for (ScriptObject obj = this; obj != null; obj = obj.getProto()) {
+                if (obj instanceof WithObject) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Add a filter to the first argument of {@code methodHandle} that calls its {@link #getProto()} method
+     * {@code depth} times.
+     * @param methodHandle a method handle
+     * @param depth        distance to target prototype
+     * @return the filtered method handle
+     */
+    static MethodHandle addProtoFilter(final MethodHandle methodHandle, final int depth) {
+        if (depth == 0) {
+            return methodHandle;
+        }
+        final int listIndex = depth - 1; // We don't need 0-deep walker
+        MethodHandle filter = listIndex < protoFilters.size() ? protoFilters.get(listIndex) : null;
+
+        if(filter == null) {
+            filter = addProtoFilter(GETPROTO, depth - 1);
+            protoFilters.add(null);
+            protoFilters.set(listIndex, filter);
+        }
+
+        return MH.filterArguments(methodHandle, 0, filter.asType(filter.type().changeReturnType(methodHandle.type().parameterType(0))));
+    }
+
+    /**
      * Find the appropriate GET method for an invoke dynamic call.
      *
      * @param desc     the call site descriptor
@@ -1722,7 +1762,7 @@
      */
     protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) {
         final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
-        if (request.isCallSiteUnstable()) {
+        if (request.isCallSiteUnstable() || hasWithScope()) {
             return findMegaMorphicGetMethod(desc, name, "getMethod".equals(operator));
         }
 
@@ -1748,22 +1788,24 @@
         final Property property = find.getProperty();
         methodHandle = find.getGetter(returnType);
 
+        final boolean noGuard = ObjectClassGenerator.OBJECT_FIELDS_ONLY && NashornCallSiteDescriptor.isFastScope(desc) && !property.canChangeType();
         // getMap() is fine as we have the prototype switchpoint depending on where the property was found
-        final MethodHandle guard = NashornGuards.getMapGuard(getMap());
+        final MethodHandle guard = noGuard ? null : NashornGuards.getMapGuard(getMap());
 
         if (methodHandle != null) {
             assert methodHandle.type().returnType().equals(returnType);
             if (find.isSelf()) {
-                return new GuardedInvocation(methodHandle, ObjectClassGenerator.OBJECT_FIELDS_ONLY &&
-                        NashornCallSiteDescriptor.isFastScope(desc) && !property.canChangeType() ? null : guard);
+                return new GuardedInvocation(methodHandle, guard);
             }
 
-            final ScriptObject prototype = find.getOwner();
-
-            if (!property.hasGetterFunction(prototype)) {
-                methodHandle = bindTo(methodHandle, prototype);
+            if (!property.hasGetterFunction(find.getOwner())) {
+                // If not a scope bind to actual prototype as changing prototype will change the property map.
+                // For scopes we install a filter that replaces the self object with the prototype owning the property.
+                methodHandle = isScope() ?
+                        addProtoFilter(methodHandle, find.getProtoChainLength()) :
+                        bindTo(methodHandle, find.getOwner());
             }
-            return new GuardedInvocation(methodHandle, getMap().getProtoGetSwitchPoint(proto, name), guard);
+            return new GuardedInvocation(methodHandle, noGuard ? null : getMap().getProtoGetSwitchPoint(proto, name), guard);
         }
 
         assert !NashornCallSiteDescriptor.isFastScope(desc);
@@ -1833,7 +1875,7 @@
      */
     protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
         final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
-        if (request.isCallSiteUnstable()) {
+        if (request.isCallSiteUnstable() || hasWithScope()) {
             return findMegaMorphicSetMethod(desc, name);
         }
 
@@ -2761,7 +2803,8 @@
     public final void setObject(final FindProperty find, final boolean strict, final String key, final Object value) {
         FindProperty f = find;
 
-        if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty)) {
+        if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty) && !isScope()) {
+            // Setting a property should not modify the property in prototype unless this is a scope object.
             f = null;
         }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/SetMethodCreator.java	Tue Jan 07 18:14:18 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/SetMethodCreator.java	Tue Jan 07 14:16:23 2014 +0100
@@ -151,10 +151,12 @@
         assert methodHandle != null;
         assert property     != null;
 
-        final ScriptObject prototype = find.getOwner();
         final MethodHandle boundHandle;
-        if (!property.hasSetterFunction(prototype) && find.isInherited()) {
-            boundHandle = ScriptObject.bindTo(methodHandle, prototype);
+        if (!property.hasSetterFunction(find.getOwner()) && find.isInherited()) {
+            // Bind or add prototype filter depending on whether this is a scope object.
+            boundHandle = sobj.isScope() ?
+                    ScriptObject.addProtoFilter(methodHandle, find.getProtoChainLength()):
+                    ScriptObject.bindTo(methodHandle, find.getOwner());
         } else {
             boundHandle = methodHandle;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8029667.js	Tue Jan 07 14:16:23 2014 +0100
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010, 2013, 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-8029667: Prototype linking is incorrect
+ *
+ * @test
+ * @run
+ */
+
+function f(x) {
+  return (function inner() { 
+      var y; (function dummy() { return y })() // force own scope for the inner function
+      with({}) { // 'with' block turns off fast scopes
+          return x
+      }
+  })();
+}
+print(f(1));
+print(f(2));
+
+function g(x) { 
+  (function inner() { 
+      var y; (function dummy() { return y })() // force own scope for the inner function
+      with({}) { // 'with' block turns off fast scopes
+          // Test setter as well as getter
+          x = x + 2;
+      }
+  })();
+  print(x);
+}
+
+g(1);
+g(2);
+
+var withScopes = [{ func: function() { print("called 1");} }, { func: function() { print("called 2");} }];
+
+for(var i in withScopes) {
+    with (withScopes[i]) {
+        var main = function() {
+            var frame; // <---- this local variable caused scope to be not set properly prior to fix
+
+            function callFunc() {
+                frame = func();
+            }
+
+            callFunc();
+        }
+    }
+    main();
+}
+
+for(var i in withScopes) {
+    with (withScopes[i]) {
+        var main = function() {
+            var frame; // <---- this local variable caused scope to be not set properly prior to fix
+
+            function callFunc() {
+                frame = func = i;
+            }
+
+            callFunc();
+        }
+    }
+    main();
+} 
+
+print(withScopes[0].func);
+print(withScopes[1].func);
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8029667.js.EXPECTED	Tue Jan 07 14:16:23 2014 +0100
@@ -0,0 +1,8 @@
+1
+2
+3
+4
+called 1
+called 2
+0
+1