More fun with scopes and ScriptObjectMirror

A. Sundararajan sundararajan.athijegannathan at oracle.com
Wed Dec 11 05:28:24 PST 2013


Our emails crossed (again!). I suggested that option based on avatar/js 
code..

Sundar

On Wednesday 11 December 2013 06:40 PM, Tim Fox 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).
>>
>> 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 also considered a third option - executing all modules in a single 
> global, but wrapping the module code in a function to hide any top 
> level globals declared as vars, e.g. if module is:
>
> var someglobal = "hello";
> module.exports = someglobal;
>
> after wrapping it becomes:
>
> (function(module) {
>   var someglobal = "hello");
>   module.exports = someglobal;
> })();
>
> which hides someglobal.
>
> However this doesn't work with modules that use globals by omitting 
> var, e.g.
>
> someglobal = "hello";
> module.exports = someglobal;
>
> Now, there are far fewer CommonJS modules  which use globals without 
> var (as it's bad practice) but still enough to make this not a good 
> option either :(
>
>
>
>>
>> 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