More fun with scopes and ScriptObjectMirror
A. Sundararajan
sundararajan.athijegannathan at oracle.com
Thu Dec 12 01:12:20 PST 2013
But when you do
engine.eval("foo = 2");
this will *not* modify GLOBAL_SCOPE Bindings "foo". Instead it will
create a new variable in nashorn Global object with the name "foo"
(shadowing GLOBAL_SCOPE's "foo"). This is what I meant by "read only"
java Bindings variables in one of my earlier emails.
PS. Implementation note: every object can have a special
__noSuchProperty__ function property. This is invoked whenever a
property is not found in the object. Nashorn script engine installs
__noSuchProperty__ on nashorn's Global object and in that function it
searches Bindings of ScriptContext for any missing property (if not
found in ScriptContext's Bindings, ReferenceError is thrown)
-Sundar
On Thursday 12 December 2013 02:36 PM, A. Sundararajan wrote:
> You could still have GLOBAL_SCOPE Bindings (or any intermediate scope
> that you may introduce in your impl. of ScriptContext) between those
> contexts be different. Even ENGINE_SCOPE Bindings could be different
> -- say a SimpleBindings instance. Single shared Nashorn global is
> associated with that ENGINE_SCOPE. But when an undefined global var is
> searched from script, after searching the nashorn's Global, search
> continues in the Bindings of ScriptContext. So, you can continue to
> expose different variable values.
>
> Example:
>
> ScriptContext defCtx = engine.getContext();
> defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");
>
> ScriptContext myCtx = new SimpleScriptContext();
> myCtx.setBindings(defCtx.getBindings(ScriptContext.ENGINE_SCOPE),
> ScriptContext.ENGINE_SCOPE);
> Bindings b = new SimpleBindings();
> b.put("foo", "world");
> myCtx.setBindings(b, ScriptContext.GLOBAL_SCOPE);
>
> engine.eval("print(foo)"); // prints 'hello'
> engine.eval("print(foo)", myCtx); // prints "world"
> engine.eval("print(foo)", defCtx); // prints "hello"
>
> PS. compilable test attached.
>
> -Sundar
>
>
> On Thursday 12 December 2013 02:19 PM, Attila Szegedi wrote:
>> On Dec 12, 2013, at 8:00 AM, Tim Fox <timvolpe at gmail.com> wrote:
>>
>>> On 11/12/13 12:53, Attila Szegedi wrote:
>>>> On Dec 11, 2013, at 1:13 PM, Tim Fox <timvolpe at gmail.com> wrote:
>>>>
>>>>> Confused...
>>>>>
>>>>> I assumed that if two scripts where run with their own script
>>>>> context, then they would already have separate globals, i.e. if I do
>>>>>
>>>>> myglobal = 1
>>>>>
>>>>> in module 1 that won't be visible in module 2.
>>>> That's true, but then you also end up with the need for
>>>> ScriptObjectMirrors between them, and that was what I suggested you
>>>> try to avoid.
>>>>
>>>>> So I'm not sure really what --global-per-engine really means, if
>>>>> the modules have their own globals anyway. I guess my
>>>>> understanding must be wrong somewhere.
>>>> Well, it will mean that modules won't have their own globals…
>>>> --global-per-engine will make it so that the Global object is
>>>> specific to the ScriptEngine, and not to the ScriptContext, e.g.
>>>> even if you replace the ScriptContext of the engine, when scripts
>>>> are run, they'll still see the same global object as before the
>>>> replacement. The gist of it is:
>>>>
>>>> a) without --global-per-engine, the Global object is specific to a
>>>> ScriptContext, each ScriptContext has its own. ENGINE_SCOPE
>>>> Bindings object of the context is actually a mirror of its Global.
>>>> b) with --global-per-engine, the Global object lives in the
>>>> ScriptEngine. ENGINE_SCOPE Bindings object of the context is just a
>>>> vanilla SimpleBindings (or whatever you set it to), and Global will
>>>> delegate property getters for non-existent properties to it (but
>>>> it'll still receive property setters, so new global variables
>>>> created by one script will be visible by another; no isolation there).
>>> One more question on this. With --global-per-engine, and multiple
>>> ScriptContext instances - if all the ScriptContext instances share a
>>> single a single global, how is this different from just having a
>>> single ScriptContext in which you execute all JavaScript?
>> Not much. Using separate ScriptContexts with single engine is pretty
>> much equivalent to modifying the initial ScriptContext of the engine.
>> When I thought you'll end up using Java code to implement module
>> loading, I suggested either using separate contexts, or at least
>> changing the ENGINE_SCOPE bindings of the original context to contain
>> "module", "exports", etc. but if you now implement those as
>> parameters of an anonymous function that you put around the module
>> source code and eval(), then you don't need even that.
>>
>> So, yeah, you can have a single context for all JavaScript. As I said
>> earlier, it'll be single-threaded, but JavaScript the language is by
>> nature single-threaded (that's why node.js is single-threaded too).
>> Probably the better way to say it is that JavaScript language has no
>> defined multithreaded semantics, and thus a JavaScript execution
>> environment is unsafe to use in a multithreaded manner. You can
>> always have multiple engines – either per-thread or pooled – but
>> they'll be completely isolated from one another, and modules and
>> other code will end up being loaded separately in them. If you want
>> shared state between them, you can always plug some shared Java
>> objects into their ENGINE_SCOPE bindings or have them access some
>> stateful statics, and then ensure that those Java objects are
>> threadsafe.
>>
>> But I have now digressed a lot from your original question.
>>
>> Attila.
>>
>>>> What I was suggesting is that your module loading code would look
>>>> something like:
>>>>
>>>> // The engine that you use
>>>> ScriptEngine engine = new NashornScriptEngine().getEngine(new
>>>> String[] { "--global-per-engine" });
>>>>
>>>> ...
>>>>
>>>> // when loading a module
>>>> Bindings moduleVars = new SimpleBindings();
>>>> moduleVars.put("require", requireFn);
>>>> moduleVars.put("module", moduleDescriptorObj);
>>>> moduleVars.put("exports", exportsObj);
>>>> Bindings prevBindings = engine.getBindings(ENGINE_SCOPE);
>>>> engine.setBindings(moduleVars, ENGINE_SCOPE);
>>>> try {
>>>> engine.eval(moduleSource);
>>>> } finally {
>>>> engine.setBindings(prevBindings, ENGINE_SCOPE);
>>>> }
>>>> return exportsObj;
>>>>
>>>> NB: your modules would _not_ run in isolated globals. I thought
>>>> they do, but I just spoke to Sundar and he explained the mechanism
>>>> to me so now I see they won't -- see above the case b).
>>>>
>>>> They could pollute each other's global namespace (since it's
>>>> shared). Hopefully they'd adhere to Modules/Secure recommendation
>>>> and refrain from doing so, but you can't really enforce it.
>>>>
>>>> My require() implementation in Rhino could provide real isolation,
>>>> but this is unfortunately impossible in Nashorn. Nashorn makes an
>>>> assumption that the global object during execution of a script is
>>>> an instance of jdk.nashorn.internals.objects.Global; in Rhino, it
>>>> could have been anything so there I was able to run a module in a
>>>> new scope that had the actual Global object as its prototype, so it
>>>> could catch all variable assignments in itself and essentially make
>>>> the Global read only (albeit objects in it mutable - e.g. a module
>>>> could still extend Array prototype etc.).
>>>>
>>>> In Nashorn, it's the other way round with "--global-per-engine" -
>>>> Global object is the one immediately visible to scripts, and
>>>> ENGINE_SCOPE Bindings object is used as source of properties that
>>>> aren't found in the Global. Here, Global catches variable
>>>> assignments and ENGINE_SCOPE Bindings object ends up being
>>>> immutable (although objects in it are obviously still mutable, so
>>>> the module can build up its "exports" object).
>>>>
>>>> Basically, you have a choice between having shared globals (no
>>>> isolation) without mirrors (and then you won't run into any issues
>>>> with mirrors), or separate globals (with real isolation), but then
>>>> also mirrors, and then you might run into limitations of mirrors
>>>> (e.g. they can't be automatically used as Runnable etc. callbacks
>>>> from Java and so on.)
>>>>
>>>> I'm not trying to justify or otherwise qualify any of the design
>>>> decisions here, just trying to help you understand its constraints.
>>>>
>>>> Attila.
>
More information about the nashorn-dev
mailing list