More fun with scopes and ScriptObjectMirror
Tim Fox
timvolpe at gmail.com
Wed Dec 11 09:43:26 PST 2013
So probably I will take the same path as you guys and just do a pure JS
require and wrap in anon. function. Nice and easy
Thanks for all your help!
On 11/12/13 14:27, A. Sundararajan wrote:
> avatar/js project passes the relevant node.js tests (tests that check
> no leak of vars from module files). Apparently, none of the modules
> have "var" less declarations.
>
> -Sundar
>
> On Wednesday 11 December 2013 07:13 PM, Tim Fox wrote:
>> On 11/12/13 13:28, A. Sundararajan wrote:
>>>
>>> Our emails crossed (again!).
>>
>> Hehe, we really must stop doing that! ;)
>>
>>> I suggested that option based on avatar/js code..
>>
>> fwiw, this is also the approach used in rhino-require
>> https://github.com/micmath/Rhino-Require
>>
>> It's actually the first thing I considered because it's so simple,
>> and it's pure JS so portable between engines. However this approach
>> is flawed as it doesn't prevent the leakage of globals not declared
>> using var, i.e.
>>
>> someglobal = 3;
>>
>> Which is what led me down the more complex route with explicitly
>> manipulating scopes at the engine level...
>>
>> Do you guys not consider the leakage of non var globals not a big
>> issue? Personally I ruled out this approach because of that, but
>> maybe I should reconsider it (?)
>>
>> In the long term though, I think it would be nice if Nashorn provided
>> the mechanism to implement require() with real isolation.
>>
>>
>>
>>
>>>
>>> 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