blob: 15c56122c7d0a48a05da7ccafd3d7d94be4e948c [file] [log] [blame]
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "node.h"
#include "node_script.h"
#include <assert.h>
namespace node {
using v8::Context;
using v8::Script;
using v8::Value;
using v8::Handle;
using v8::HandleScope;
using v8::Object;
using v8::Arguments;
using v8::ThrowException;
using v8::TryCatch;
using v8::String;
using v8::Exception;
using v8::Local;
using v8::Array;
using v8::Persistent;
using v8::Integer;
using v8::Function;
using v8::FunctionTemplate;
class WrappedContext : ObjectWrap {
public:
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments& args);
Persistent<Context> GetV8Context();
static Local<Object> NewInstance();
static bool InstanceOf(Handle<Value> value);
protected:
static Persistent<FunctionTemplate> constructor_template;
WrappedContext();
~WrappedContext();
Persistent<Context> context_;
};
Persistent<FunctionTemplate> WrappedContext::constructor_template;
class WrappedScript : ObjectWrap {
public:
static void Initialize(Handle<Object> target);
enum EvalInputFlags { compileCode, unwrapExternal };
enum EvalContextFlags { thisContext, newContext, userContext };
enum EvalOutputFlags { returnResult, wrapExternal };
template <EvalInputFlags input_flag,
EvalContextFlags context_flag,
EvalOutputFlags output_flag>
static Handle<Value> EvalMachine(const Arguments& args);
protected:
static Persistent<FunctionTemplate> constructor_template;
WrappedScript() : ObjectWrap() {}
~WrappedScript();
static Handle<Value> New(const Arguments& args);
static Handle<Value> CreateContext(const Arguments& arg);
static Handle<Value> RunInContext(const Arguments& args);
static Handle<Value> RunInThisContext(const Arguments& args);
static Handle<Value> RunInNewContext(const Arguments& args);
static Handle<Value> CompileRunInContext(const Arguments& args);
static Handle<Value> CompileRunInThisContext(const Arguments& args);
static Handle<Value> CompileRunInNewContext(const Arguments& args);
Persistent<Script> script_;
};
Persistent<Function> cloneObjectMethod;
void CloneObject(Handle<Object> recv,
Handle<Value> source, Handle<Value> target) {
HandleScope scope;
Handle<Value> args[] = {source, target};
// Init
if (cloneObjectMethod.IsEmpty()) {
Local<Function> cloneObjectMethod_ = Local<Function>::Cast(
Script::Compile(String::New(
"(function(source, target) {\n\
Object.getOwnPropertyNames(source).forEach(function(key) {\n\
try {\n\
var desc = Object.getOwnPropertyDescriptor(source, key);\n\
if (desc.value === source) desc.value = target;\n\
Object.defineProperty(target, key, desc);\n\
} catch (e) {\n\
// Catch sealed properties errors\n\
}\n\
});\n\
})"
), String::New("binding:script"))->Run()
);
cloneObjectMethod = Persistent<Function>::New(cloneObjectMethod_);
}
cloneObjectMethod->Call(recv, 2, args);
}
void WrappedContext::Initialize(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(WrappedContext::New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(String::NewSymbol("Context"));
target->Set(String::NewSymbol("Context"),
constructor_template->GetFunction());
}
bool WrappedContext::InstanceOf(Handle<Value> value) {
return !value.IsEmpty() && constructor_template->HasInstance(value);
}
Handle<Value> WrappedContext::New(const Arguments& args) {
HandleScope scope;
WrappedContext *t = new WrappedContext();
t->Wrap(args.This());
return args.This();
}
WrappedContext::WrappedContext() : ObjectWrap() {
context_ = Context::New();
}
WrappedContext::~WrappedContext() {
context_.Dispose();
}
Local<Object> WrappedContext::NewInstance() {
Local<Object> context = constructor_template->GetFunction()->NewInstance();
return context;
}
Persistent<Context> WrappedContext::GetV8Context() {
return context_;
}
Persistent<FunctionTemplate> WrappedScript::constructor_template;
void WrappedScript::Initialize(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(WrappedScript::New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
// Note: We use 'NodeScript' instead of 'Script' so that we do not
// conflict with V8's Script class defined in v8/src/messages.js
// See GH-203 https://github.com/joyent/node/issues/203
constructor_template->SetClassName(String::NewSymbol("NodeScript"));
NODE_SET_PROTOTYPE_METHOD(constructor_template,
"createContext",
WrappedScript::CreateContext);
NODE_SET_PROTOTYPE_METHOD(constructor_template,
"runInContext",
WrappedScript::RunInContext);
NODE_SET_PROTOTYPE_METHOD(constructor_template,
"runInThisContext",
WrappedScript::RunInThisContext);
NODE_SET_PROTOTYPE_METHOD(constructor_template,
"runInNewContext",
WrappedScript::RunInNewContext);
NODE_SET_METHOD(constructor_template,
"createContext",
WrappedScript::CreateContext);
NODE_SET_METHOD(constructor_template,
"runInContext",
WrappedScript::CompileRunInContext);
NODE_SET_METHOD(constructor_template,
"runInThisContext",
WrappedScript::CompileRunInThisContext);
NODE_SET_METHOD(constructor_template,
"runInNewContext",
WrappedScript::CompileRunInNewContext);
target->Set(String::NewSymbol("NodeScript"),
constructor_template->GetFunction());
}
Handle<Value> WrappedScript::New(const Arguments& args) {
if (!args.IsConstructCall()) {
return FromConstructorTemplate(constructor_template, args);
}
HandleScope scope;
WrappedScript *t = new WrappedScript();
t->Wrap(args.Holder());
return
WrappedScript::EvalMachine<compileCode, thisContext, wrapExternal>(args);
}
WrappedScript::~WrappedScript() {
script_.Dispose();
}
Handle<Value> WrappedScript::CreateContext(const Arguments& args) {
HandleScope scope;
Local<Object> context = WrappedContext::NewInstance();
if (args.Length() > 0) {
if (args[0]->IsObject()) {
Local<Object> sandbox = args[0].As<Object>();
CloneObject(args.This(), sandbox, context);
} else {
return ThrowException(Exception::TypeError(String::New(
"createContext() accept only object as first argument.")));
}
}
return scope.Close(context);
}
Handle<Value> WrappedScript::RunInContext(const Arguments& args) {
return
WrappedScript::EvalMachine<unwrapExternal, userContext, returnResult>(args);
}
Handle<Value> WrappedScript::RunInThisContext(const Arguments& args) {
return
WrappedScript::EvalMachine<unwrapExternal, thisContext, returnResult>(args);
}
Handle<Value> WrappedScript::RunInNewContext(const Arguments& args) {
return
WrappedScript::EvalMachine<unwrapExternal, newContext, returnResult>(args);
}
Handle<Value> WrappedScript::CompileRunInContext(const Arguments& args) {
return
WrappedScript::EvalMachine<compileCode, userContext, returnResult>(args);
}
Handle<Value> WrappedScript::CompileRunInThisContext(const Arguments& args) {
return
WrappedScript::EvalMachine<compileCode, thisContext, returnResult>(args);
}
Handle<Value> WrappedScript::CompileRunInNewContext(const Arguments& args) {
return
WrappedScript::EvalMachine<compileCode, newContext, returnResult>(args);
}
template <WrappedScript::EvalInputFlags input_flag,
WrappedScript::EvalContextFlags context_flag,
WrappedScript::EvalOutputFlags output_flag>
Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
HandleScope scope;
if (input_flag == compileCode && args.Length() < 1) {
return ThrowException(Exception::TypeError(
String::New("needs at least 'code' argument.")));
}
const int sandbox_index = input_flag == compileCode ? 1 : 0;
if (context_flag == userContext
&& !WrappedContext::InstanceOf(args[sandbox_index]))
{
return ThrowException(Exception::TypeError(
String::New("needs a 'context' argument.")));
}
Local<String> code;
if (input_flag == compileCode) code = args[0]->ToString();
Local<Object> sandbox;
if (context_flag == newContext) {
sandbox = args[sandbox_index]->IsObject() ? args[sandbox_index]->ToObject()
: Object::New();
} else if (context_flag == userContext) {
sandbox = args[sandbox_index]->ToObject();
}
const int filename_index = sandbox_index +
(context_flag == thisContext? 0 : 1);
Local<String> filename = args.Length() > filename_index
? args[filename_index]->ToString()
: String::New("evalmachine.<anonymous>");
const int display_error_index = args.Length() - 1;
bool display_error = false;
if (args.Length() > display_error_index &&
args[display_error_index]->IsBoolean() &&
args[display_error_index]->BooleanValue() == true) {
display_error = true;
}
Handle<Context> context = Context::GetCurrent();
Local<Array> keys;
if (context_flag == newContext) {
// Create the new context
// Context::New returns a Persistent<Context>, but we only need it for this
// function. Here we grab a temporary handle to the new context, assign it
// to a local handle, and then dispose the persistent handle. This ensures
// that when this function exits the context will be disposed.
Persistent<Context> tmp = Context::New();
context = Local<Context>::New(tmp);
tmp.Dispose();
} else if (context_flag == userContext) {
// Use the passed in context
WrappedContext *nContext = ObjectWrap::Unwrap<WrappedContext>(sandbox);
context = nContext->GetV8Context();
}
Context::Scope context_scope(context);
// New and user context share code. DRY it up.
if (context_flag == userContext || context_flag == newContext) {
// Copy everything from the passed in sandbox (either the persistent
// context for runInContext(), or the sandbox arg to runInNewContext()).
CloneObject(args.This(), sandbox, context->Global()->GetPrototype());
}
// Catch errors
TryCatch try_catch;
Handle<Value> result;
Handle<Script> script;
if (input_flag == compileCode) {
// well, here WrappedScript::New would suffice in all cases, but maybe
// Compile has a little better performance where possible
script = output_flag == returnResult ? Script::Compile(code, filename)
: Script::New(code, filename);
if (script.IsEmpty()) {
// FIXME UGLY HACK TO DISPLAY SYNTAX ERRORS.
if (display_error) DisplayExceptionLine(try_catch);
// Hack because I can't get a proper stacktrace on SyntaxError
return try_catch.ReThrow();
}
} else {
WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.Holder());
if (!n_script) {
return ThrowException(Exception::Error(
String::New("Must be called as a method of Script.")));
} else if (n_script->script_.IsEmpty()) {
return ThrowException(Exception::Error(
String::New("'this' must be a result of previous "
"new Script(code) call.")));
}
script = n_script->script_;
}
if (output_flag == returnResult) {
result = script->Run();
if (result.IsEmpty()) {
if (display_error) DisplayExceptionLine(try_catch);
return try_catch.ReThrow();
}
} else {
WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.Holder());
if (!n_script) {
return ThrowException(Exception::Error(
String::New("Must be called as a method of Script.")));
}
n_script->script_ = Persistent<Script>::New(script);
result = args.This();
}
if (context_flag == userContext || context_flag == newContext) {
// success! copy changes back onto the sandbox object.
CloneObject(args.This(), context->Global()->GetPrototype(), sandbox);
}
return result == args.This() ? result : scope.Close(result);
}
void InitEvals(Handle<Object> target) {
HandleScope scope;
WrappedContext::Initialize(target);
WrappedScript::Initialize(target);
}
} // namespace node
NODE_MODULE(node_evals, node::InitEvals)