8044000: Access to undefined property yields "null" instead of "undefined"
Reviewed-by: lagergren, jlaskey
--- 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));
+ }
}