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