8044000: Access to undefined property yields "null" instead of "undefined"
authorsundar
Tue, 27 May 2014 17:40:19 +0530
changeset 24591 67e22e0e8473
parent 24590 0d2c811d2ad1
child 24592 ce0717dfefda
8044000: Access to undefined property yields "null" instead of "undefined" Reviewed-by: lagergren, jlaskey
nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java
nashorn/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java
nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java
nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Mon May 26 15:48:25 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Tue May 27 17:40:19 2014 +0530
@@ -61,9 +61,17 @@
     private static final DynamicLinker dynamicLinker;
     static {
         final DynamicLinkerFactory factory = new DynamicLinkerFactory();
-        factory.setPrioritizedLinkers(new NashornLinker(), new NashornPrimitiveLinker(), new NashornStaticClassLinker(),
-                new BoundDynamicMethodLinker(), new JavaSuperAdapterLinker(), new JSObjectLinker(), new ReflectionCheckLinker());
-        factory.setFallbackLinkers(new NashornBeansLinker(), new NashornBottomLinker());
+        final NashornBeansLinker nashornBeansLinker = new NashornBeansLinker();
+        final JSObjectLinker jsObjectLinker = new JSObjectLinker(nashornBeansLinker);
+        factory.setPrioritizedLinkers(
+            new NashornLinker(),
+            new NashornPrimitiveLinker(),
+            new NashornStaticClassLinker(),
+            new BoundDynamicMethodLinker(),
+            new JavaSuperAdapterLinker(),
+            jsObjectLinker,
+            new ReflectionCheckLinker());
+        factory.setFallbackLinkers(nashornBeansLinker, new NashornBottomLinker());
         factory.setSyncOnRelink(true);
         final int relinkThreshold = Options.getIntProperty("nashorn.unstable.relink.threshold", -1);
         if (relinkThreshold > -1) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Mon May 26 15:48:25 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Tue May 27 17:40:19 2014 +0530
@@ -30,6 +30,7 @@
 import java.lang.invoke.MethodType;
 import java.util.HashMap;
 import java.util.Map;
+import javax.script.Bindings;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.GuardedTypeConversion;
@@ -48,14 +49,23 @@
  * as ScriptObjects from other Nashorn contexts.
  */
 final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory {
+    private final NashornBeansLinker nashornBeansLinker;
+
+    JSObjectLinker(final NashornBeansLinker nashornBeansLinker) {
+        this.nashornBeansLinker = nashornBeansLinker;
+    }
+
     @Override
     public boolean canLinkType(final Class<?> type) {
         return canLinkTypeStatic(type);
     }
 
     static boolean canLinkTypeStatic(final Class<?> type) {
-        // can link JSObject
-        return JSObject.class.isAssignableFrom(type);
+        // can link JSObject also handles Map, Bindings to make
+        // sure those are not JSObjects.
+        return Map.class.isAssignableFrom(type) ||
+               Bindings.class.isAssignableFrom(type) ||
+               JSObject.class.isAssignableFrom(type);
     }
 
     @Override
@@ -72,6 +82,11 @@
         final GuardedInvocation inv;
         if (self instanceof JSObject) {
             inv = lookup(desc);
+        } else if (self instanceof Map || self instanceof Bindings) {
+            // guard to make sure the Map or Bindings does not turn into JSObject later!
+            final GuardedInvocation beanInv = nashornBeansLinker.getGuardedInvocation(request, linkerServices);
+            inv = new GuardedInvocation(beanInv.getInvocation(),
+                NashornGuards.combineGuards(beanInv.getGuard(), NashornGuards.getNotJSObjectGuard()));
         } else {
             throw new AssertionError(); // Should never reach here.
         }
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java	Mon May 26 15:48:25 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java	Tue May 27 17:40:19 2014 +0530
@@ -31,6 +31,7 @@
 import java.lang.invoke.MethodHandles;
 import java.lang.ref.WeakReference;
 import jdk.internal.dynalink.CallSiteDescriptor;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.codegen.ObjectClassGenerator;
 import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.runtime.Property;
@@ -43,6 +44,7 @@
  */
 public final class NashornGuards {
     private static final MethodHandle IS_SCRIPTOBJECT   = findOwnMH("isScriptObject", boolean.class, Object.class);
+    private static final MethodHandle IS_NOT_JSOBJECT   = findOwnMH("isNotJSObject", boolean.class, Object.class);
     private static final MethodHandle IS_SCRIPTFUNCTION = findOwnMH("isScriptFunction", boolean.class, Object.class);
     private static final MethodHandle IS_MAP            = findOwnMH("isMap", boolean.class, Object.class, PropertyMap.class);
     private static final MethodHandle SAME_OBJECT       = findOwnMH("sameObject", boolean.class, Object.class, WeakReference.class);
@@ -61,6 +63,14 @@
     }
 
     /**
+     * Get the guard that checks if an item is not a {@code JSObject}
+     * @return method handle for guard
+     */
+    public static MethodHandle getNotJSObjectGuard() {
+        return IS_NOT_JSOBJECT;
+    }
+
+    /**
      * Get the guard that checks if an item is a {@code ScriptFunction}
      * @return method handle for guard
      */
@@ -157,6 +167,11 @@
     }
 
     @SuppressWarnings("unused")
+    private static boolean isNotJSObject(final Object self) {
+        return !(self instanceof JSObject);
+    }
+
+    @SuppressWarnings("unused")
     private static boolean isScriptFunction(final Object self) {
         return self instanceof ScriptFunction;
     }
--- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Mon May 26 15:48:25 2014 +0530
+++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Tue May 27 17:40:19 2014 +0530
@@ -29,6 +29,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import javax.script.Bindings;
+import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
@@ -276,4 +278,31 @@
             "({ toString: function() { return 'foo' } })");
         assertEquals("foo", obj.to(String.class));
     }
+
+    // @bug 8044000: Access to undefined property yields "null" instead of "undefined"
+    @Test
+    public void mapScriptObjectMirrorCallsiteTest() throws ScriptException {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine engine = m.getEngineByName("nashorn");
+        final String TEST_SCRIPT = "typeof obj.foo";
+
+        final Bindings global = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
+        engine.eval("var obj = java.util.Collections.emptyMap()");
+        // this will drive callsite "obj.foo" of TEST_SCRIPT
+        // to use "obj instanceof Map" as it's guard
+        engine.eval(TEST_SCRIPT, global);
+        // redefine 'obj' to be a script object
+        engine.eval("obj = {}");
+
+        final Bindings newGlobal = engine.createBindings();
+        // transfer 'obj' from default global to new global
+        // new global will get a ScriptObjectMirror wrapping 'obj'
+        newGlobal.put("obj", global.get("obj"));
+
+        // Every ScriptObjectMirror is a Map! If callsite "obj.foo"
+        // does not see the new 'obj' is a ScriptObjectMirror, it'll
+        // continue to use Map's get("obj.foo") instead of ScriptObjectMirror's
+        // getMember("obj.foo") - thereby getting null instead of undefined
+        assertEquals("undefined", engine.eval(TEST_SCRIPT, newGlobal));
+    }
 }