Using === across different contexts
Sundararajan Athijegannathan
sundararajan.athijegannathan at oracle.com
Wed Dec 23 02:58:07 UTC 2015
Hi,
Addressing your question on j.u.IdentityHashMap. I assume the rest of
the questions were addressed by Attila.
* Assume that we implement === for ScriptObjectMirror to be (JVM) object
identity comparison of the *wrapped* underlying objects (as in your
attempted patch) rather than identity of ScriptObjectMirror objects,
what would happen?
Script writer would see === being true for potentially two *different*
ScriptObjectMirror instances. Based on the mental model that === means
strict object identity, s/he then may try to use ScriptObjectMirror objects
in a java.util.IdentityHashMap! IdentityHashMap itself would use JVM
object identity -- so two different ScriptObjectMirror instances m1, m2
wrapping *same* underlying script object "sobj" are different
for IdentityHashMap implementation. But, this breaks the assumption by
the script writer! s/he thinks m1 === m2 true => IdentityHashMap should
treat m1 and m2 are identical! That is not the case and so model breaks
for script authors. That is why I said, if at all we want to preserve
script object identity with ScriptObjectMirror instances, we'd need to
implement weakref based mirroring.
-Sundar
On 12/23/2015 1:41 AM, Vivin Suresh Paliath wrote:
> Hi Sundar,
>
> Thank you for the explanation! I hope I am not being too obtuse - I'm
> just trying to get a handle on how things work!
>
> 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
>
>
> In my view, ScriptObjectMirrors should be treated the same only if it
> is made abundantly clear that the JavaScript object you're working
> with in Nashorn, may sometimes be a wrapped object and not the actual
> object itself. The problem is that right now there is no easy way to
> discern that, and anticipate the side effects that come with that
> distinction. With respect to the ES spec, I agree that === should
> evaluate to true if the objects are the same object, and that holds
> true for the most part even if the objects are wrapped. But the
> problem arises when you try to compare a property of an object (which
> is itself a JS object) with itself, where the parent object happens to
> be foreign. That's when you end up in a situation where obj === obj
> returns true, but obj.prop === obj.prop returns false. The assumption
> I am making is invalid only if I /know/ that they are mirror objects,
> but as I mentioned before, there is no easy way to determine that. I
> can see a case where obj.prop is actually a getter that returns a new
> instance of an object each time, and here obj.prop === obj.prop would
> return false, but that fact would be evident from the source of the JS
> object itself, and wouldn't be due to how foreign script-objects are
> implemented in Nashorn.
>
> 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!
>
>
> Perhaps I am not understanding this example correctly. Are you saying
> that inserts into the IdentityHashMap wouldn't work as expected? But
> wouldn't the only comparison that is being performed be == in Java,
> which would be comparing the ScriptObjectMirror instances anyway
> (i.e., the same behavior as now). I tried out the following with and
> without my changes and I got the same output (size of the map is 3):
>
> 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));
>
> c.getBindings(ScriptContext.ENGINE_SCOPE).put("ihm", new
> IdentityHashMap<Object, Object>());
> engine.eval("ihm.put(e.x, 1); ihm.put(e.x, 2); ihm.put(e.x, 3);
> print(ihm.size());", c);
>
>
> The one discrepancy that would exist is if you perform an ===
> comparison in JavaScript against something from the map.
>
> 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".
>
>
> The new contexts I create know nothing about each other; they only
> "know" (in the sense that the "global" runtime-library objects are
> available) the objects from the main context (and only because I copy
> them over). Also, I am not treating the object from the main
> isolated-world the same way as the one in the current one; I am really
> only trying to compare references (and that too against themselves).
>
> 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
>
>
> This is actually exactly what I am doing, and I am using require-like
> functionality as well. The problem arises when I try to dereference a
> property of an object that is a foreign script-object. For example,
> let's assume the default context of the script engine has an "enum"
> SomeEnum, with the enum CONST. When I get a custom script, I create a
> new context (using createBindings) and copy everything over. So now
> the new context has a foreign object called SomeEnum. Now if I do
> SomeEnum === SomeEnum, I get back true, but SomeEnum.CONST ===
> SomeEnum.CONST returns false. Is this what you were saying weak-refs
> would solve?
>
> 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.
>
>
> That would be a workaround, but some code that I insert are
> autogenerated JS equivalents of POJOs. Enums are implemented using
> this <https://github.com/vivin/enumjs> library, which gives you
> type-safe enums in JavaScript as well. So I cannot assume upfront what
> kind of Java enums are going to be used since they come from a
> different project entirely.
>
> Based on what you said, I agree with you that === is probably the
> wrong place to do this and it seems that the thing that really needs
> to be fixed is ensuring that properties of foreign objects return the
> same mirror instance whenever dereferenced. As I said before, I can't
> think of a case where obj.prop === obj.prop would return false
> /unless/ prop is a calculated property that returns a new instance of
> some object each time. Even if that were the case, I don't see how
> that behavior would be broken if the Nashorn runtime ensured that we
> got back the same mirror instance. Regardless, wouldn't it notice that
> prop actually delegates to a getter function, and then run that? But
> in the case where prop is a reference to an actual object, you would
> still have the expected behavior. I understand that these foreign
> objects are wrapped, but I am questioning whether someone really needs
> to /know/ that they are, if they are writing JS code on Nashorn.
>
> My original aim when I was doing this was to provide a single
> script-engine instance so that I could take advantage of optimizations
> on common code, while providing isolated execution contexts. Objects
> in the engine's default context are "read-only" and the isolated
> contexts cannot really mess with them. Really what I'm after is
> ensuring that library-code gets optimized over time and that contexts
> can't step over each other. So if such an approach gives me that, then
> it would definitely make things a lot easier. Also, is there somewhere
> I can read about the optimizations Nashorn makes in different cases?
> I'm specifically wondering about the following:
>
> 1. Does Nashorn optimize pre-compiled JS code each time it is
> evaluated? Are the optimizations persisted to the compiled
> representation?
> 2. If I repeatedly evaluate the same JS code in the same
> script-engine, but in different contexts, is that code optimized?
> Or are the optimizations discarded once the context is gone?
>
> If I can still get these optimizations, then I can simply just
> precompile and/or re-evaluate the library code in each new context and
> that would do away with this issue and make things much easier.
>
> Thank you once again, and I apologize for so many questions!
>
> Vivin
>
> On Tue, Dec 22, 2015 at 1:35 AM, Sundararajan Athijegannathan
> <sundararajan.athijegannathan at oracle.com
> <mailto:sundararajan.athijegannathan at oracle.com>> wrote:
>
> 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
>>> |
>>
>
>
>
>
> --
> 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