More fun with scopes and ScriptObjectMirror

Tim Fox timvolpe at gmail.com
Wed Dec 11 05:22:38 PST 2013


On 11/12/13 13:19, A. Sundararajan wrote:
> The way Avatar/js project ( https://java.net/projects/avatar-js ) 
> project implements CommonJS/require is as follows.
>
> It creates a anonymous function code wrapping a module (say like 
> http.js). The anonymous function accepts 'exports' as argument. When 
> you eval that code at top level global scope, because of the anon 
> function wrapping around, all top level vars in a module code like 
> http.js become locals of that anon function. 

Isn't that the same as what I described in my last post?

If so, the problem with that is that globals that aren't prefixed with 
var still leak.

> Only whatever is passed as 'exports' is populated with exposed 
> functions and variables. The caller can pass suitable 'exports' object 
> as argument when calling the anon functions.
>
> -Sundar
>
> On Wednesday 11 December 2013 06:34 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.
>>
>> That's the problem. CommonJS modules need to run in their own globals 
>> to avoid pollution.
>>
>>> 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.
>>
>> Just about every CommonJS module I have seen uses globals, e.g. 
>> pretty much all the node.js ones (and the vert.x ones). Here's an 
>> example:
>>
>> https://github.com/joyent/node/blob/master/lib/http.js
>>
>> Notice the liberal use of top level vars, which are globals. This is 
>> the norm in CommonJS AIUI. Any require() implementation that doesn't 
>> provide global isolation isn't going to fly.
>>
>>
>>>
>>> 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.)
>>
>> Mirrors also don't seem to work in the way required for a require() 
>> implementation, as described in my previous experiments :(
>>
>> So... the way it looks right now, I can't see anyway I can create a 
>> working commonJS require() implementation with Nashorn as it stands 
>> currently. This would be a showstopper for having Nashorn support in 
>> Vert.x which is dissappointing as I was impressed by the good 
>> performance :(
>>
>> Having said that, apparently avatar.js does implement require() so 
>> I'm puzzled how it manages that. I did look at the avatar.js code but 
>> couldn't find their require implementation. Does anyone have any 
>> pointers?
>>>
>>> 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