Is there any way to create a Handle<ObjectTemplate> from a Handle<Object>?

87 views
Skip to first unread message

Isaac Z. Schlueter

unread,
Mar 1, 2009, 7:34:11 PM3/1/09
to v8-users
I'm trying to write a function that will execute a bit of Javascript
within an arbitrary global scope. Something like the "evalcx"
function in the Spidermonkey shell.

I've got a function defined and exposed to the Javascript that takes
an object as an argument, and stores it as a Handle<Object>. However,
in order to create a new execution context, I need a
Handle<ObjectTemplate>.

Is there an easy way to do this?

Thanks!

--i

Stephan Beal

unread,
Mar 1, 2009, 8:06:03 PM3/1/09
to v8-users
On Mar 2, 1:34 am, "Isaac Z. Schlueter" <i...@foohack.com> wrote:
> I'm trying to write a function that will execute a bit of Javascript
> within an arbitrary global scope.  Something like the "evalcx"
> function in the Spidermonkey shell.
>
> I've got a function defined and exposed to the Javascript that takes
> an object as an argument, and stores it as a Handle<Object>.  However,
> in order to create a new execution context, I need a
> Handle<ObjectTemplate>.

Do you need a new Context, or just an object to eval the code in? If
you just need an Object to execute the code in...

They way i ended up doing this was getting the 'eval' function from my
local object, then using Call() on that Function object:

TryCatch tryer;
Local<Value> rv;
Local<Function> eval = Function::Cast( *(db->jsobj->Get(String::New
("eval"))) );
// ^^^^^ db->jsobj is my context object
for( int i = 0; i < argc; ++i )
{
char const * cp = reinterpret_cast<char const *>
( sqlite3_value_text( argv[i] ) );
if( ! cp || !*cp ) continue;
Local<Value> arg = String::New( cp, sqlite3_value_bytes( argv
[i] ) );
rv = eval->Call( db->jsobj, 1, &arg );
// ^^^^ that will, in theory, eval the code in the context of db-
>jsobj
if( rv.IsEmpty() )
{
std::string err( CastFromJS<std::string>( tryer.Exception() ) );
sqlite3_result_text( context, err.c_str(), static_cast<int>(err.size
()), SQLITE_TRANSIENT );
return;
}
}

Isaac Z. Schlueter

unread,
Mar 2, 2009, 12:05:33 PM3/2/09
to v8-users
Thanks, Stephan.

It looks like what you're doing will execute the code and set "this"
to the object in question.

However, what I'd really like to do is execute code and set a given
object as the global scope. Here's an example with some code:

var code = "var foo = 'bar';";
var obj = { foo : "baz" };
evalcx( code, obj ); // execute code using "obj" as the global object
print( obj.foo ); // prints "bar"

Unless I'm misreading your example, what you're describing will work
in this case:

var code = "this.foo = 'bar';";
var obj = { foo : "baz" };

But, I can already do that from JS like so:

(new Function(code)).call(obj);

If I could eval code in an arbitrary global scope within Javascript,
which did not have any access to the global scope, then it would be
possible to implement a capability-based security model.

Also, it would make it possible to load and execute many different
scripts in a row without having to "clean up" the globals that each
one might create. After being evaluated, the temporary global scope
could be discarded, and would be garbage collected normally.

For example, let's say you have a fastcgi handler written in
Javascript, which reads and evals the SCRIPT_FILENAME file. The
problem is that, since the evaled code shares a global scope with the
fcgi handler, it also shares a global scope with *every other script
loaded by that handler.* That means that every global variable is
persistent in memory, and only gets garbage collected when you restart
the server. Not ideal! :)

--
Isaac Z. Schlueter

Alex Iskander

unread,
Mar 2, 2009, 12:18:34 PM3/2/09
to v8-u...@googlegroups.com
I just found this in v8.h:
/** Creates a new context. */
  static Persistent<Context> New(
      ExtensionConfiguration* extensions = 0,
      Handle<ObjectTemplate> global_template = Handle<ObjectTemplate>(),
      Handle<Value> global_object = Handle<Value>());

You may be able to use this to call Context::New(0, GlobalTemplate, GlobalObject);

Where GlobalObject is the object passed to evalcx.

However, it may not work; I'm not certain what would happen if you pass an object belonging to another context as a global object of a new context.

So, plan B might be to manually copy the items from your global object passed to evalcx to the global object for your new context.

I hope that helps,

Alex
Alex Iskander
Web and Marketing
TPSi



Isaac Z. Schlueter

unread,
Mar 2, 2009, 4:10:43 PM3/2/09
to v8-users


On Mar 2, 9:18 am, Alex Iskander <a...@tpsitulsa.com> wrote:
> You may be able to use this to call Context::New(0, GlobalTemplate,  
> GlobalObject);
>
> Where GlobalObject is the object passed to evalcx.

Thanks, Alex.

That seems to do two things:
1. the code executes in a new context with no globals.
2. the object passed to evalcx is set to null. (!?)

Here's my code:

// the c++ function
static v8::Handle<v8::Value> EvalCX (const v8::Arguments& args)
{
v8::HandleScope handlescope;
v8::String::Utf8Value code(args[0]);
v8::Local<v8::Object> sandbox = args[1]->ToObject();
v8::Handle<v8::String> source = v8::String::New(*code);

// Create a new execution environment for sandbox
// problem here.
v8::Handle<v8::Context> context = v8::Context::New(NULL,
Handle<ObjectTemplate>(), sandbox);

// Enter the newly created execution environment.
v8::Context::Scope context_scope(context);

v8::Handle<v8::Script> script = v8::Script::Compile(source,
v8::String::New("evalcx"));
if (script.IsEmpty()) return v8::ThrowException(v8::String::New
("Error parsing script"));

// destroys the sandbox object!?
v8::Handle<v8::Value> result = script->Run();
if (result.IsEmpty()) return v8::ThrowException(v8::String::New
("Error running script"));

return result;
}

// the binding to global in main()
global->Set(v8::String::New("evalcx"), v8::FunctionTemplate::New
(EvalCX)->GetFunction());

// the js
code = "foo = 1; bar = 2;";
foo = 2;
o = { foo : 0 };
evalcx(code, obj);
print( o.foo ); // expected: 1. actual: error, o is null
print( o.bar ); // expected: 2. actual: error, o is null
print( foo ); // expected: 2. actual: 2
print( bar ); // expected: undefined. actual: undefined

So, it looks like it's taking the code out of the caller's global
context, and putting it into a new context. That's great. However,
it's also destroying the object that gets passed in, rather than
modifying it.

Alex Iskander

unread,
Mar 2, 2009, 5:03:04 PM3/2/09
to v8-u...@googlegroups.com
Your problem is apparently that the same object cannot be in multiple
contexts at once.

Or, more likely, it cannot be the global object of one context and
still be used in another context. That would explain the presence of a
DetachGlobal (or something like that) function in Context to allow you
to re-use the context's global object.

You can get the object back; if you use Context::DetachGlobal() and
Context::Global(), you should be able to use the object again. The
only problem is that the original object still won't be modified -- it
will still have been set to NULL. But then again, do you really want
to allow one context to modify a variable from another? If your sub-
context can modify an object in the original context, you might have a
security concern.

For instance, what if you used this:
var obj = {"foo": this}; //passing the global scope
evalcx(code, obj); //we just gave the other context our global scope.
Security!

Of course, if you have complete, 100% control over the code calling
evalcx, this wouldn't be a problem, I suppose.

A possibly simpler and more secure alternative might be to pass the
object back. Your JavaScript code would then look like:

obj = evalcx(code, obj);

Also, just wondering, why do they need to be global variables? Why
have the variables be global, instead of members of an object passed
to a function? You could then just create a new context, load the
script, find the script's function, and pass it the object. You'd
still have the security of multiple contexts, but things would be a
lot simpler.

Alex Iskander

Isaac Z. Schlueter

unread,
Mar 2, 2009, 6:58:27 PM3/2/09
to v8-users
Alex,

Thanks for the insight. That's very helpful.

I do have complete control over the code calling evalcx. I also can
assume that I have more-or-less complete control over the code being
evaluated, though that might be used by someone who is not quite as
savvy about the problems with leaking contexts.

Here's the application:

A fastcgi script, written in Javascript, loads the file that was
requested by the user. It interprets that file's syntax, converting
it to "regular" Javascript. Then it evaluates the script server-side,
and outputs the results.

So, I'm actually not too concerned with exposing the global object,
since all the script is in-house, so to speak. However, I'm quite
concerned about accidentally creating a global variable (ie, if I
misplace a "var" somewhere), and having that variable stick around
until the server is restarted (since the script and the fcgi share a
context.)

> var obj = {"foo": this}; //passing the global scope
> evalcx(code, obj); //we just gave the other context our global scope.

That's actually fine, at least for my application. You can enable
another script to use your global context, but you still can't "get
outside the box". I can give you keys to my house, but I can't steal
the keys to your house without you giving them to me. And while you
might do "foo.blah = 1", you'll at least *know* that you're creating a
persistent shared global variable, rather than just doing it by
accident.

> A possibly simpler and more secure alternative might be to pass the
> object back. Your JavaScript code would then look like:
>
> obj = evalcx(code, obj);

That would be interesting. Couldn't your code just do something like
"return this;" to accomplish the same thing? If evalcx worked that
way, then it would not be possible to use it to return a computation
or something. I suppose it could create a new global which would be
accessible via "obj" in this case. Maybe that's not such a big deal,
I don't know. I haven't thought about the ideal return value too
much. Knowing about success or failure would be handy.

> Also, just wondering, why do they need to be global variables?

Because that's what happens when you're writing code late at night
trying out a new idea, and you forget to put "var" in front of
something. ;)

It's not that globals are wonderful, per se, but rather that
supporting globals means that the system will not be able to spring
random leaks and have variables that never get garbage collected.

Some other ideas that I've thrown around:

1. In js-land, create a single read-only object on the global scope
that is shared between scripts. After evaling, delete any new globals
that show up.
2. In c-land, create a new empty Handle<ObjectTemplate> like I'm
doing, and then loop through the properties of the supplied object,
and Set them on the ObjectTemplate as well.

However, it seems like there must be a better way. After all, the
function prototypes are ObjectTemplates (well, PrototypeTemplates,
which are convertible), and they can be set from a js object, right?

--i

Alex Iskander

unread,
Mar 2, 2009, 9:56:29 PM3/2/09
to v8-u...@googlegroups.com
I'm glad to help.

ObjectTemplates and PrototypeTemplates cannot be set/modified in JS. Prototypes in JS are actual objects, that are used as prototypes. FunctionTemplates, and their sub-templates PrototypeTemplates and InstanceTemplates, are all templates to create the actual Function, Prototype, and Instance objects.

I still think that you may not need to use global variables -- at least, not exactly.

It is possible to pass the v8::Objects between contexts. I actually just spend a few minutes testing that functionality, and it worked. I think the reason why it didn't work with the global object is that the Global object has to belong specifically to one context or another, hence the DetachGlobal or whatever method. 

For example, assume we have two js files:

//js1.js
var obj = {foo: "bar"};
var result = runRequest(js2js, obj); //where runRequest is a native C++ function
print(global); //prints "undefined"
print(result.test); //prints "succeeded"

//js2.js:
var global = "whatever";
function run()
{
print(this.foo); //prints "bar";
return {"test": "succeeded"};
}

print(global); //prints "whatever"


And some C++ code for runRequest();
//really psuedo-code for runRequest:
HandleScope handle_scope;
context = Context::New();
{
Context::Scope context_scope(context);
Script::Run();
Function f = Context::Global()->Get("run");
result = f->Call(arguments[0]);
}
context.Dispose(), etc.

return result;


In this scenario, the Global object in each context is kept separate. The only item shared between them is obj, which is passed as the "this" argument to run() in js2.js.

Alex
Alex Iskander, TPSi




Isaac Z. Schlueter

unread,
Mar 8, 2009, 11:03:03 PM3/8/09
to v8-users
So, I did eventually get this to work very much like SpiderMonkey's
evalcx function. Here's the code if anyone else is interested:

usage:
evalcx( code_to_execute, global_object (default: this), keep_changes
(bool, should changed global be written back to the object?) )
returns the result of the executed code.

static v8::Handle<v8::Value> EvalCX (const v8::Arguments& args)
{
HandleScope handle_scope;
Handle<Context> context = Context::New();
context->SetSecurityToken(
Context::GetCurrent()->GetSecurityToken()
);

Context::Scope context_scope(context);
Handle<String> code = args[0]->ToString();
Handle<Object> sandbox = args.Length() >= 1 ? args[1]->ToObject() :
Context::GetCurrent()->Global();

// share global datas
copy_obj( sandbox, context->Global());
context->Enter();

Handle<Script> script = Script::Compile(code, String::New("evalcx"));
Handle<Value> result;
if (script.IsEmpty()) {
result = ThrowException(String::New("Error parsing script"));
} else {
result = script->Run();
if (result.IsEmpty()) {
result = v8::ThrowException(v8::String::New("Error running
script"));
}
}
if (args.Length() >= 3 && args[2]->IsTrue()) {
copy_obj( context->Global(), sandbox );
}
context->DetachGlobal();
context->Exit();
return result;
}

--i
Reply all
Reply to author
Forward
0 new messages