Using === across different contexts
Sundararajan Athijegannathan
sundararajan.athijegannathan at oracle.com
Tue Dec 22 08:35:31 UTC 2015
Hi,
May be I should explain the background on how nashorn treates
"foreign/host" objects. Nashorn implements object access primitives
like "get_property, set_property, get_method, call, new" via a series of
dynalink linkers (http://openjdk.java.net/jeps/276). There is a linker
for it's own objects (ScriptObject instances), there is one for Java
objects ("beans linkers"), "foreign 'script' objects"
(ScriptObjectMirrors). It allows even user specified linkers picked up
via java.util.ServiceLoader mechanism. (See
http://hg.openjdk.java.net/jdk9/dev/nashorn/file/74ddd1339c57/samples/dynalink).
But, looping (via for, for..each), identity and other operators are
natively implemented in Nashorn. Object identity is implemented strictly
per ECMAScript spec - which says === evals to be evaluated true only if
"same object". Why should we treat ScriptObjectMirrors as special for
identity comparison (and who knows what problems it could cause than the
ones it solves!). For eg. think of using that in j.u.IdentityHashMap and
expect it work! [=== returns true and so IdentityHashMap should work as
expected -- but it won't because IdentityHashMap goes by JVM level
object identity! As I said, it at all we want mirrors to preserve
identity, we need weak refs.
/
Regarding host objects, even if they are different from native objects,
it seems strange to me that the semantics of things like === would be
different especially when doing something like obj.prop === obj.prop.
The fact that such a statement could ever return false is non-obvious
unless implementation details of the runtime are known.
/
The fact that you're assuming your host objects return "same object" for
doing two "obj.prop" evals -- is an assumption about mirror objects! Why
do you think such as assumption to be true for mirrors -- whose sole
purpose is to provide easy access to objects. You're creating multiple
ScriptContexts and associate ENGINE_SCOPE Bindings to isolate. And yet
you want objects from "isolated" worlds to be treated exactly like
"current isolate world".
If you do not want that kind of multiple ECMAScript global scope
isolation at all, why not load your library/framework scripts before
loading the user scripts? If you want to avoid clash b/w different user
scripts (i.e., globals in user's scripts for eg.), you could implement
"require" like primitive such as the one here ->
https://github.com/walterhiggins/commonjs-modules-javax-script
If you do want isolation and still want singleton enum-like objects, why
not implement those in Java and expose to scripts? These would then be
real JVM singleton objects and therefore usual identity would work.
Hope this helps,
-Sundar
On 12/22/2015 12:25 PM, Vivin Suresh Paliath wrote:
>
> I am looking at it from the perspective of someone writing code that
> is expect to run in JavaScript environment. I will describe the
> runtime environment I have set up. Perhaps what I am running into is
> an artifact of my approach.
>
> I have a single script engine instance. In this context I evaluate
> some JS source that populates the JS global scope with some objects.
> When a custom script needs to be evaluated, I create a new script
> context with a new global. Then I copy over all bindings from the
> parent script context into the new one. I also have a module system.
> When a custom script requests a core module, the module source is
> evaluated in the main script context and a single instance of the
> object exposed by the module source is cached. This way the source
> only needs to be evaluated once and benefits from optimizations.
>
> The problem I am running into has to do with some singleton objects I
> expose; kind of like enums, and can be compared using ===. I am using
> a JS library for this and this behavior holds in the browser and on
> NodeJS. On Nashorn, because the instances were created in a different
> context, === returns false.
>
> Is there a better way to accomplish what I am doing?
>
> Regarding host objects, even if they are different from native
> objects, it seems strange to me that the semantics of things like ===
> would be different especially when doing something like obj.prop ===
> obj.prop. The fact that such a statement could ever return false is
> non-obvious unless implementation details of the runtime are known.
>
> I am trying to expose my runtime environment as a JS environment where
> a developer can write custom scripts using some runtime libraries and
> utilities. So behavior like this would be very surprising, and
> requiring them to know that objects might be from different contexts
> seems like abstraction leakage from the way I have implemented the
> runtime. They shouldn't have to know whether an object is a wrapped
> ScriptObjectMirror instance, since from their point of view they are
> just working with JS objects, and they don't know that they are from
> different "worlds" since they don't even know that there are different
> worlds.
>
> I guess I am also having a hard time seeing a scenario where === would
> need to return false in the scenario I described, unless as a
> developer you knew there were different worlds. Even then, it would
> essentially make some forms of equality comparisons impossible (e.g.,
> o.prop === o.prop if o is foreign).
>
> Does the snippet I posted earlier make sense?
>
> On Dec 21, 2015 9:10 PM, "Sundararajan Athijegannathan"
> <sundararajan.athijegannathan at oracle.com
> <mailto:sundararajan.athijegannathan at oracle.com>> wrote:
>
> Actually, I'm not sure if depending on === in the code is a good
> approach -- particularly, for objects that are not script objects
> of the current world. These are to be treated like "host objects"
> in ECMAScript-speak. i.e., regular rules of script objects don't
> always apply to 'host objects'. The === operator for Java objects
> is interpreted as object identity -- and the same rule for
> ScriptObjectMirrors -- in that both are "host objects" from the
> standpoint of the 'current world'.
>
> -Sundar
>
> On 12/22/2015 9:34 AM, Vivin Suresh Paliath wrote:
>> Thanks for the response! I understand that in general it would be
>> difficult for foreign objects. But this seems like surprising
>> behavior given that these are both JavaScript objects. That they
>> are unequal seems to be an artifact of the implementation (the
>> fact that JS objects from different contexts are treated as
>> foreign). Could JS objects be treated differently?
>>
>> After going through the Nashorn source, I decided to try this
>> very naive approach adding the following test to
>> ScriptRuntime#equalSameTypeValues:
>>
>> if(x instanceof ScriptObjectMirror && y instanceof
>> ScriptObjectMirror) {
>> return x.equals(y);
>> }
>>
>> After this change, the code now returns true, because
>> ScriptObjectMirror#equals compares the actual objects. I am not
>> sure if this breaks anything though (been trying to run the test
>> suite, but end up getting some errors from make). Is there a
>> reason this particular fix is a bad idea? I can't think of a
>> particular reason why. From the perspective of the runtime, I
>> can't see a reason why those two objects should be considered
>> different.
>>
>> I will investigate the eval approach, but ideally I would like
>> something that doesn't impose any changes on the JS code. I am
>> developing a simple runtime that exposes some objects and
>> utilities, and where custom scripts can run in their own
>> contexts. The fact that === returns false in these cases leads to
>> some very strange behavior.
>>
>> On Mon, Dec 21, 2015 at 8:48 PM, Sundararajan Athijegannathan
>> <sundararajan.athijegannathan at oracle.com
>> <mailto:sundararajan.athijegannathan at oracle.com>> wrote:
>>
>> Unless we create mirrors as weak refs internally (i.e.,
>> maintain 1:1 with underlying foreign object reference), there
>> is no easy solution. And maintaining such weak refs is
>> unnecessarily complex. "Foreign object" call/access is mean
>> to be just a "lightweight wrapper" based access. That said,
>> you can do the === on the foreign context itself. You can
>> call ScriptObjectMirror's eval to evaluate === test in that
>> foreign context. That would get right object identity.
>>
>> -Sundar
>>
>>
>> On 12/22/2015 4:26 AM, Vivin Suresh Paliath wrote:
>>
>> One more thing I noticed is that apparently a new
>> ScriptObjectMirror
>> instance is probably being created each time x is
>> dereferenced, so "e.x ===
>> e.x" also returns "false".
>>
>> On Mon, Dec 21, 2015 at 3:49 PM, Vivin Suresh Paliath <
>> vivin.paliath at gmail.com <mailto:vivin.paliath at gmail.com>>
>> wrote:
>>
>> I ran into an issue where === returns false even when
>> both should be
>> pointing to the same object. I'm assuming this is
>> because one of the
>> objects is wrapped by a ScriptObjectMirror, because
>> it was defined in a
>> different context.
>>
>> Here's some code that demonstrates this:
>>
>> ScriptEngine engine = new
>> NashornScriptEngineFactory().getScriptEngine(
>> new String[] { "-strict" }
>> );
>>
>> try {
>> engine.eval("function Foo(src) {
>> this.src = src }; var e = {
>> x: new Foo(\"what\") };");
>>
>> ScriptContext c = new SimpleScriptContext();
>> c.setBindings(engine.createBindings(),
>> ScriptContext.ENGINE_SCOPE);
>>
>> c.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE));
>>
>> System.out.println(engine.eval("var z = e.x; z ===
>> e.x;", c));
>> } catch(Exception e) {
>> throw new RuntimeException(e);
>> }
>>
>> This prints out "false". Is there a way around this?
>> I am also explicitly
>> copying over all the bindings from the parent scope
>> into the new scope so
>> that I have access to "e". Could this be the source
>> of the problem, and if
>> so, is there a better way to achieve what I'm trying
>> to do?
>>
>> --
>> Ruin untold;
>> And thine own sadness,
>> Sing in the grass,
>> When eve has forgot, that no more hear common things
>> that gleam and pass;
>> But seek alone to lip, sad Rose of love and ruin untold;
>> And thine own mother
>> Can know it as I know
>> More than another
>> What makes your own sadness,
>> Set in her eyes.
>>
>> map{@n=split//;$j.=$n[0]x$n[1]}split/:/,"01:11:02".
>> ":11:01:11:02:13:01:11:01:11:01:13:02:12:01:13:01".
>> ":11:04:11:06:12:04:11:01:12:01:13:02:12:01:14:01".
>> ":13:01:11:03:12:01:11:04:12:02:11:01:11:01:13:02".
>> ":11:03:11:06:11:01:11:05:12:02:11:01:11:01:13:02".
>> ":11:02:12:01:12:04:11:06:12:01:11:04:12:04:11:01".
>> ":12:03:12:01:12:01:11:01:12:01:12:02:11:01:11:01".
>> ":13:02:11:01:02:11:01:12:02";map{print chr unpack"
>> i",pack"B32",$_}$j=~m/.{8}/g
>>
>>
>>
>>
>>
>>
>>
>> --
>> Ruin untold;
>> And thine own sadness,
>> Sing in the grass,
>> When eve has forgot, that no more hear common things that gleam
>> and pass;
>> But seek alone to lip, sad Rose of love and ruin untold;
>> And thine own mother
>> Can know it as I know
>> More than another
>> What makes your own sadness,
>> Set in her eyes.|
>>
>> map{@n=split//;$j.=$n[0]x$n[1]}split/:/,"01:11:02".
>> ":11:01:11:02:13:01:11:01:11:01:13:02:12:01:13:01".
>> ":11:04:11:06:12:04:11:01:12:01:13:02:12:01:14:01".
>> ":13:01:11:03:12:01:11:04:12:02:11:01:11:01:13:02".
>> ":11:03:11:06:11:01:11:05:12:02:11:01:11:01:13:02".
>> ":11:02:12:01:12:04:11:06:12:01:11:04:12:04:11:01".
>> ":12:03:12:01:12:01:11:01:12:01:12:02:11:01:11:01".
>> ":13:02:11:01:02:11:01:12:02";map{print chr unpack"
>> i",pack"B32",$_}$j=~m/.{8}/g
>> |
>
More information about the nashorn-dev
mailing list