Isolated scope eval()

126 views
Skip to first unread message

Christoph Dorn

unread,
Sep 20, 2011, 4:23:08 PM9/20/11
to gp...@googlegroups.com
Is there something better than?:

with(scope)
{
eval(code);
}

e.g. nodejs:

VM.runInNewContext(code, sandbox, file);

Christoph

Wes Garland

unread,
Sep 20, 2011, 5:22:13 PM9/20/11
to gp...@googlegroups.com

The semantics here aren't completely clear to me.  Is the sandbox somehow  creating a prototype relationship between the outside modules and the sandbox?  What happens in the sandbox when it, say, modifies Array.prototype? What does it return?

We can probably rig something up with GPSEE, but we have to be careful here. In particular it's worth nothing that super-global relationships with scoping objects are not specified by ECMAScript and a rich target for elimination by JS vendors as they tend to have measureable performance side effects vs. straight symbol tables.

This is why, for example, the scoping argument was recently removed from eval in the Mozilla version of JavaScript.  It might still be there in GPSEE, though, but it will NOT be there in GPSEE 0.3.  I don't know if they will keep it in Rhino or not.

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/eval

Wes

--
Wesley W. Garland
Director, Product Development
PageMail, Inc.
+1 613 542 2787 x 102

Christoph Dorn

unread,
Sep 20, 2011, 7:45:11 PM9/20/11
to gp...@googlegroups.com
Hmm. nodejs's VM.runInNewContext(code, sandbox, file) is not very well documented but I think it works like:

--- isolated gloabls
   with(scope)
   {
       eval(code);
   }
---

Not sure if modifying prototypes affect parent.

For my case it is fine if modifying prototype in sandbox affects sandboxe's parent prototype.

The eval is used to compile module code. The objective is to only pass in "module", maybe "console" and nothing else should exist in global scope for the module. If globals are created by the module they should be local to the module.

"sandbox" is not really the right term here I don't think.

Using:

   with(scope)
   {
       eval(code);
   } 


works but causes globals to be present that in a "strict" environment should not be.

So given:

  var global = {foo:true};

  with(globals) {
     with(scope)
     {
         eval('typeof foo === "undefined"');
     } 
  }

should evaluate to TRUE.

The limitation here is that if a module does modify a prototype it *IS* global for the whole runtime which would be a restriction placed on the module author.

Not sure if all this makes sense. We can chat about it.

Christoph

Donny Viszneki

unread,
Sep 20, 2011, 8:36:09 PM9/20/11
to gp...@googlegroups.com
On Tue, Sep 20, 2011 at 7:45 PM, Christoph Dorn
<chri...@christophdorn.com> wrote:
> Hmm. nodejs's VM.runInNewContext(code, sandbox, file) is not very well
> documented but I think it works like:
>
> --- isolated gloabls
>    with(scope)
>    {
>        eval(code);
>    }
> ---
>
> Not sure if modifying prototypes affect parent.

You could use this code in GPSEE, but with(scope) does not imply that
local variable assignments will assign to properties on scope. For
example:

js> var o = {foo:'bar'}
js> with(o){print(foo)}
bar
js> with(o){bar='baz'}
"baz"
js> bar
"baz"

You can, though, shadow global variables:

js> with(o){foo='presto!'}
"presto!"
js> o.foo
"presto!"
js> foo
typein:6: ReferenceError: foo is not defined

To really sandbox though you need to shadow every global variable.
There are also still a number of ways to access the built-in object
prototypes and the global variable scope. It's may also be worth
noting that, IIRC, with() will not trace (JIT optimize) in
Spidermonkey.

> For my case it is fine if modifying prototype in sandbox affects sandboxe's
> parent prototype.

Then you have a string, which means you can use some simple
language-transformation techniques to create a function scope around
your module code before evaluating it. For instance if your module
code reads:

exports.Ball = function Ball(radius){this.volume =
Math.PI*4/3*radius*radius*radius}

Then simply wrapping that code with other code will give you the desired effect:

(function(exports){ // added
exports.Ball = function Ball(radius){this.volume =
Math.PI*4/3*radius*radius*radius}
}) // added

> The eval is used to compile module code. The objective is to only pass in
> "module", maybe "console" and nothing else should exist in global scope for
> the module. If globals are created by the module they should be local to the
> module.
>
> "sandbox" is not really the right term here I don't think.

It appears you are writing a custom module loader, you may also want
to shadow require(). Take a look at BravoJS:

http://code.google.com/p/bravojs/

I think Wes can supply you with examples if they are not forthcoming.
BravoJS was developed for web browsers, but the same principles and
some of the code apply to both environments.

> Using:
>
>    with(scope)
>    {
>        eval(code);
>    }
>
> works but causes globals to be present that in a "strict" environment should
> not be.

You will need to shadow any global variables you don't want showing
up. You can do this with function arguments, but just as good or
perhaps better to shadow them with "var" declarations:

(function(exports){ // added
var secretFunction = void 0; // added
exports.Ball = function Ball(radius){this.volume =
Math.PI*4/3*radius*radius*radius}
}) // added

Now the code will not have naive access to secretFunction.
Unfortunately there are still ways to access the global variable
scope.

> So given:
>
>   var global = {foo:true};
>
>   with(globals) {
>      with(scope)
>      {
>          eval('typeof foo === "undefined"');
>      }
>   }
>
> should evaluate to TRUE.

This is another way to do it :)

> The limitation here is that if a module does modify a prototype it *IS*
> global for the whole runtime which would be a restriction placed on the
> module author.

Correct. It is unfortunate.

Good to hear your input! I have considered modifying Spidermonkey to
return treat accessors of properties on "global prototypes"
differently depending on where the code that is accessing them came
from; the goal being to give different modules their own prototypes
for Object, Array, etc.

--
http://codebad.com/

Christoph Dorn

unread,
Sep 20, 2011, 9:03:44 PM9/20/11
to gp...@googlegroups.com
Donny Viszneki wrote:

It appears you are writing a custom module loader, you may also want
to shadow require(). Take a look at BravoJS:

http://code.google.com/p/bravojs/

I think Wes can supply you with examples if they are not forthcoming.
BravoJS was developed for web browsers, but the same principles and
some of the code apply to both environments.
Yes: https://github.com/pinf/loader-js

It is actually built on top of bravojs.

The loader shims various engines/platforms to provide a standard CommonJS environment to build portable apps on.

See: http://groups.google.com/group/gpsee/msg/8d44cae2b3997d22




> The limitation here is that if a module does modify a prototype it *IS*
> global for the whole runtime which would be a restriction placed on the
> module author.
Correct. It is unfortunate.

Good to hear your input! I have considered modifying Spidermonkey to
return treat accessors of properties on "global prototypes"
differently depending on where the code that is accessing them came
from; the goal being to give different modules their own prototypes
for Object, Array, etc.

I think AdobeAir does something like that. Also jetpack is moving towards secured modules. Not sure where that is at. In jetpack globals are already gone, not sure about modifying prototypes but they are making heavy use of proxies now to only expose what is safe/intended.

The loader I am building currently uses with(scope){eval(code);} to eval modules in an I guess "dirty and unsafe" context. The goal is to use proper "sandboxes" and object-capability based access to the outside once the engines start supporting this. jetpack may be the first. This is an area I will need a lot of input on over time as we try and get something working cross-platform, secure and performant.

Christoph


Wes Garland

unread,
Sep 20, 2011, 10:13:08 PM9/20/11
to gp...@googlegroups.com
> The loader I am building currently uses with(scope){eval(code);} to eval modules in an I
> guess "dirty and unsafe" context

That's an interesting approach, it's not unlike what we do inside GPSEE to provide our module loader.  But we use back-end engine API to do it.

Warning: with(scope) will be a very non-performant technique, causing JIT blacklist at last.

Given what you're doing, I'd encourage you to study the blog post I mentioned the other day that lets GSR run wrapped modules: http://gpsee.blogspot.com/2010/12/wrapped-modules-with-gpsee-02-gsr-10.html

The technique is straightfoward: we supply module.declare via the module constructor, then invoke the module factory function with fresh require/exports/module "locals", supplied by the function's argument vector, rather than "with(scope)".

The code works by accessing SpiderMonkey internals through FFI and accessing the "global" object, which is actually the module's scope object.

Is there a way that you can get the module scope need via invocation of the module factory, rather than trying to fudge up a scope?

Christoph Dorn

unread,
Sep 20, 2011, 11:10:34 PM9/20/11
to gp...@googlegroups.com
Wes Garland wrote:
> > The loader I am building currently uses with(scope){eval(code);} to
> eval modules in an I
> > guess "dirty and unsafe" context
>
> That's an interesting approach, it's not unlike what we do inside
> GPSEE to provide our module loader. But we use back-end engine API to
> do it.
>
> Warning: with(scope) will be a very non-performant technique, causing
> JIT blacklist at last.
Was using with() for convenience. I can get rid of that and declare vars
explicitly. There is only a few anyway.

> Given what you're doing, I'd encourage you to study the blog post I
> mentioned the other day that lets GSR run wrapped modules:
> http://gpsee.blogspot.com/2010/12/wrapped-modules-with-gpsee-02-gsr-10.html
>
> The technique is straightfoward: we supply module.declare via the
> module constructor, then invoke the module factory function with fresh
> require/exports/module "locals", supplied by the function's argument
> vector, rather than "with(scope)".
>
> The code works by accessing SpiderMonkey internals through FFI and
> accessing the "global" object, which is actually the module's scope
> object.
>
> Is there a way that you can get the module scope need via invocation
> of the module factory, rather than trying to fudge up a scope?

Should be able to. I'll take a look tomorrow.

Christoph

Reply all
Reply to author
Forward
0 new messages