mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
When the previous set of changes (bfff07b4) it was possible to have the
context get garbage collected while sandbox was still live. We need to
tie the lifetime of the context to the lifetime of the sandbox.
Fixes: https://github.com/nodejs/node/issues/5768
PR-URL: https://github.com/nodejs/node/pull/5786
Reviewed-By: jasnell - James M Snell <jasnell@gmail.com>
Reviewed-By: cjihrig - Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: trevnorris - Trevor Norris <trev.norris@gmail.com>
Reviewed-By: bnoordhuis - Ben Noordhuis <info@bnoordhuis.nl>
862 lines
27 KiB
C++
862 lines
27 KiB
C++
#include "node.h"
|
|
#include "node_internals.h"
|
|
#include "node_watchdog.h"
|
|
#include "base-object.h"
|
|
#include "base-object-inl.h"
|
|
#include "env.h"
|
|
#include "env-inl.h"
|
|
#include "util.h"
|
|
#include "util-inl.h"
|
|
#include "v8-debug.h"
|
|
|
|
namespace node {
|
|
|
|
using v8::AccessType;
|
|
using v8::Array;
|
|
using v8::ArrayBuffer;
|
|
using v8::Boolean;
|
|
using v8::Context;
|
|
using v8::Debug;
|
|
using v8::EscapableHandleScope;
|
|
using v8::External;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::HandleScope;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::Maybe;
|
|
using v8::MaybeLocal;
|
|
using v8::Name;
|
|
using v8::NamedPropertyHandlerConfiguration;
|
|
using v8::None;
|
|
using v8::Object;
|
|
using v8::ObjectTemplate;
|
|
using v8::Persistent;
|
|
using v8::PropertyAttribute;
|
|
using v8::PropertyCallbackInfo;
|
|
using v8::Script;
|
|
using v8::ScriptCompiler;
|
|
using v8::ScriptOrigin;
|
|
using v8::String;
|
|
using v8::TryCatch;
|
|
using v8::Uint8Array;
|
|
using v8::UnboundScript;
|
|
using v8::V8;
|
|
using v8::Value;
|
|
using v8::WeakCallbackInfo;
|
|
|
|
|
|
class ContextifyContext {
|
|
protected:
|
|
// V8 reserves the first field in context objects for the debugger. We use the
|
|
// second field to hold a reference to the sandbox object.
|
|
enum { kSandboxObjectIndex = 1 };
|
|
|
|
Environment* const env_;
|
|
Persistent<Context> context_;
|
|
|
|
public:
|
|
ContextifyContext(Environment* env, Local<Object> sandbox_obj) : env_(env) {
|
|
Local<Context> v8_context = CreateV8Context(env, sandbox_obj);
|
|
context_.Reset(env->isolate(), v8_context);
|
|
|
|
// Allocation failure or maximum call stack size reached
|
|
if (context_.IsEmpty())
|
|
return;
|
|
context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
|
|
context_.MarkIndependent();
|
|
}
|
|
|
|
|
|
~ContextifyContext() {
|
|
context_.Reset();
|
|
}
|
|
|
|
|
|
inline Environment* env() const {
|
|
return env_;
|
|
}
|
|
|
|
|
|
inline Local<Context> context() const {
|
|
return PersistentToLocal(env()->isolate(), context_);
|
|
}
|
|
|
|
|
|
inline Local<Object> global_proxy() const {
|
|
return context()->Global();
|
|
}
|
|
|
|
|
|
inline Local<Object> sandbox() const {
|
|
return Local<Object>::Cast(context()->GetEmbedderData(kSandboxObjectIndex));
|
|
}
|
|
|
|
// XXX(isaacs): This function only exists because of a shortcoming of
|
|
// the V8 SetNamedPropertyHandler function.
|
|
//
|
|
// It does not provide a way to intercept Object.defineProperty(..)
|
|
// calls. As a result, these properties are not copied onto the
|
|
// contextified sandbox when a new global property is added via either
|
|
// a function declaration or a Object.defineProperty(global, ...) call.
|
|
//
|
|
// Note that any function declarations or Object.defineProperty()
|
|
// globals that are created asynchronously (in a setTimeout, callback,
|
|
// etc.) will happen AFTER the call to copy properties, and thus not be
|
|
// caught.
|
|
//
|
|
// The way to properly fix this is to add some sort of a
|
|
// Object::SetNamedDefinePropertyHandler() function that takes a callback,
|
|
// which receives the property name and property descriptor as arguments.
|
|
//
|
|
// Luckily, such situations are rare, and asynchronously-added globals
|
|
// weren't supported by Node's VM module until 0.12 anyway. But, this
|
|
// should be fixed properly in V8, and this copy function should be
|
|
// removed once there is a better way.
|
|
void CopyProperties() {
|
|
HandleScope scope(env()->isolate());
|
|
|
|
Local<Context> context = PersistentToLocal(env()->isolate(), context_);
|
|
Local<Object> global =
|
|
context->Global()->GetPrototype()->ToObject(env()->isolate());
|
|
Local<Object> sandbox_obj = sandbox();
|
|
|
|
Local<Function> clone_property_method;
|
|
|
|
Local<Array> names = global->GetOwnPropertyNames();
|
|
int length = names->Length();
|
|
for (int i = 0; i < length; i++) {
|
|
Local<String> key = names->Get(i)->ToString(env()->isolate());
|
|
bool has = sandbox_obj->HasOwnProperty(context, key).FromJust();
|
|
if (!has) {
|
|
// Could also do this like so:
|
|
//
|
|
// PropertyAttribute att = global->GetPropertyAttributes(key_v);
|
|
// Local<Value> val = global->Get(key_v);
|
|
// sandbox->ForceSet(key_v, val, att);
|
|
//
|
|
// However, this doesn't handle ES6-style properties configured with
|
|
// Object.defineProperty, and that's exactly what we're up against at
|
|
// this point. ForceSet(key,val,att) only supports value properties
|
|
// with the ES3-style attribute flags (DontDelete/DontEnum/ReadOnly),
|
|
// which doesn't faithfully capture the full range of configurations
|
|
// that can be done using Object.defineProperty.
|
|
if (clone_property_method.IsEmpty()) {
|
|
Local<String> code = FIXED_ONE_BYTE_STRING(env()->isolate(),
|
|
"(function cloneProperty(source, key, target) {\n"
|
|
" if (key === 'Proxy') return;\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"
|
|
"})");
|
|
|
|
Local<Script> script =
|
|
Script::Compile(context, code).ToLocalChecked();
|
|
clone_property_method = Local<Function>::Cast(script->Run());
|
|
CHECK(clone_property_method->IsFunction());
|
|
}
|
|
Local<Value> args[] = { global, key, sandbox_obj };
|
|
clone_property_method->Call(global, ARRAY_SIZE(args), args);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// This is an object that just keeps an internal pointer to this
|
|
// ContextifyContext. It's passed to the NamedPropertyHandler. If we
|
|
// pass the main JavaScript context object we're embedded in, then the
|
|
// NamedPropertyHandler will store a reference to it forever and keep it
|
|
// from getting gc'd.
|
|
Local<Value> CreateDataWrapper(Environment* env) {
|
|
EscapableHandleScope scope(env->isolate());
|
|
Local<Object> wrapper =
|
|
env->script_data_constructor_function()
|
|
->NewInstance(env->context()).FromMaybe(Local<Object>());
|
|
if (wrapper.IsEmpty())
|
|
return scope.Escape(Local<Value>::New(env->isolate(), Local<Value>()));
|
|
|
|
Wrap(wrapper, this);
|
|
return scope.Escape(wrapper);
|
|
}
|
|
|
|
|
|
Local<Context> CreateV8Context(Environment* env, Local<Object> sandbox_obj) {
|
|
EscapableHandleScope scope(env->isolate());
|
|
Local<FunctionTemplate> function_template =
|
|
FunctionTemplate::New(env->isolate());
|
|
function_template->SetHiddenPrototype(true);
|
|
|
|
function_template->SetClassName(sandbox_obj->GetConstructorName());
|
|
|
|
Local<ObjectTemplate> object_template =
|
|
function_template->InstanceTemplate();
|
|
|
|
NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback,
|
|
GlobalPropertySetterCallback,
|
|
GlobalPropertyQueryCallback,
|
|
GlobalPropertyDeleterCallback,
|
|
GlobalPropertyEnumeratorCallback,
|
|
CreateDataWrapper(env));
|
|
object_template->SetHandler(config);
|
|
|
|
Local<Context> ctx = Context::New(env->isolate(), nullptr, object_template);
|
|
|
|
CHECK(!ctx.IsEmpty());
|
|
ctx->SetSecurityToken(env->context()->GetSecurityToken());
|
|
|
|
// We need to tie the lifetime of the sandbox object with the lifetime of
|
|
// newly created context. We do this by making them hold references to each
|
|
// other. The context can directly hold a reference to the sandbox as an
|
|
// embedder data field. However, we cannot hold a reference to a v8::Context
|
|
// directly in an Object, we instead hold onto the new context's global
|
|
// object instead (which then has a reference to the context).
|
|
ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj);
|
|
sandbox_obj->SetPrivate(env->context(),
|
|
env->contextify_global_private_symbol(),
|
|
ctx->Global());
|
|
|
|
env->AssignToContext(ctx);
|
|
|
|
return scope.Escape(ctx);
|
|
}
|
|
|
|
|
|
static void Init(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> function_template =
|
|
FunctionTemplate::New(env->isolate());
|
|
function_template->InstanceTemplate()->SetInternalFieldCount(1);
|
|
env->set_script_data_constructor_function(function_template->GetFunction());
|
|
|
|
env->SetMethod(target, "runInDebugContext", RunInDebugContext);
|
|
env->SetMethod(target, "makeContext", MakeContext);
|
|
env->SetMethod(target, "isContext", IsContext);
|
|
}
|
|
|
|
|
|
static void RunInDebugContext(const FunctionCallbackInfo<Value>& args) {
|
|
Local<String> script_source(args[0]->ToString(args.GetIsolate()));
|
|
if (script_source.IsEmpty())
|
|
return; // Exception pending.
|
|
Local<Context> debug_context = Debug::GetDebugContext(args.GetIsolate());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
if (debug_context.IsEmpty()) {
|
|
// Force-load the debug context.
|
|
Debug::GetMirror(args.GetIsolate()->GetCurrentContext(), args[0]);
|
|
debug_context = Debug::GetDebugContext(args.GetIsolate());
|
|
CHECK(!debug_context.IsEmpty());
|
|
// Ensure that the debug context has an Environment assigned in case
|
|
// a fatal error is raised. The fatal exception handler in node.cc
|
|
// is not equipped to deal with contexts that don't have one and
|
|
// can't easily be taught that due to a deficiency in the V8 API:
|
|
// there is no way for the embedder to tell if the data index is
|
|
// in use.
|
|
const int index = Environment::kContextEmbedderDataIndex;
|
|
debug_context->SetAlignedPointerInEmbedderData(index, env);
|
|
}
|
|
|
|
Context::Scope context_scope(debug_context);
|
|
MaybeLocal<Script> script = Script::Compile(debug_context, script_source);
|
|
if (script.IsEmpty())
|
|
return; // Exception pending.
|
|
args.GetReturnValue().Set(script.ToLocalChecked()->Run());
|
|
}
|
|
|
|
|
|
static void MakeContext(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (!args[0]->IsObject()) {
|
|
return env->ThrowTypeError("sandbox argument must be an object.");
|
|
}
|
|
Local<Object> sandbox = args[0].As<Object>();
|
|
|
|
// Don't allow contextifying a sandbox multiple times.
|
|
CHECK(
|
|
!sandbox->HasPrivate(
|
|
env->context(),
|
|
env->contextify_context_private_symbol()).FromJust());
|
|
|
|
TryCatch try_catch(env->isolate());
|
|
ContextifyContext* context = new ContextifyContext(env, sandbox);
|
|
|
|
if (try_catch.HasCaught()) {
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
|
|
if (context->context().IsEmpty())
|
|
return;
|
|
|
|
sandbox->SetPrivate(
|
|
env->context(),
|
|
env->contextify_context_private_symbol(),
|
|
External::New(env->isolate(), context));
|
|
}
|
|
|
|
|
|
static void IsContext(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (!args[0]->IsObject()) {
|
|
env->ThrowTypeError("sandbox must be an object");
|
|
return;
|
|
}
|
|
Local<Object> sandbox = args[0].As<Object>();
|
|
|
|
auto result =
|
|
sandbox->HasPrivate(env->context(),
|
|
env->contextify_context_private_symbol());
|
|
args.GetReturnValue().Set(result.FromJust());
|
|
}
|
|
|
|
|
|
static void WeakCallback(const WeakCallbackInfo<ContextifyContext>& data) {
|
|
ContextifyContext* context = data.GetParameter();
|
|
delete context;
|
|
}
|
|
|
|
|
|
static ContextifyContext* ContextFromContextifiedSandbox(
|
|
Environment* env,
|
|
const Local<Object>& sandbox) {
|
|
auto maybe_value =
|
|
sandbox->GetPrivate(env->context(),
|
|
env->contextify_context_private_symbol());
|
|
Local<Value> context_external_v;
|
|
if (maybe_value.ToLocal(&context_external_v) &&
|
|
context_external_v->IsExternal()) {
|
|
Local<External> context_external = context_external_v.As<External>();
|
|
return static_cast<ContextifyContext*>(context_external->Value());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
static void GlobalPropertyGetterCallback(
|
|
Local<Name> property,
|
|
const PropertyCallbackInfo<Value>& args) {
|
|
ContextifyContext* ctx =
|
|
Unwrap<ContextifyContext>(args.Data().As<Object>());
|
|
|
|
// Stil initializing
|
|
if (ctx->context_.IsEmpty())
|
|
return;
|
|
|
|
Local<Context> context = ctx->context();
|
|
Local<Object> sandbox = ctx->sandbox();
|
|
MaybeLocal<Value> maybe_rv =
|
|
sandbox->GetRealNamedProperty(context, property);
|
|
if (maybe_rv.IsEmpty()) {
|
|
maybe_rv =
|
|
ctx->global_proxy()->GetRealNamedProperty(context, property);
|
|
}
|
|
|
|
Local<Value> rv;
|
|
if (maybe_rv.ToLocal(&rv)) {
|
|
if (rv == sandbox)
|
|
rv = ctx->global_proxy();
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
}
|
|
}
|
|
|
|
|
|
static void GlobalPropertySetterCallback(
|
|
Local<Name> property,
|
|
Local<Value> value,
|
|
const PropertyCallbackInfo<Value>& args) {
|
|
ContextifyContext* ctx =
|
|
Unwrap<ContextifyContext>(args.Data().As<Object>());
|
|
|
|
// Stil initializing
|
|
if (ctx->context_.IsEmpty())
|
|
return;
|
|
|
|
ctx->sandbox()->Set(property, value);
|
|
}
|
|
|
|
|
|
static void GlobalPropertyQueryCallback(
|
|
Local<Name> property,
|
|
const PropertyCallbackInfo<Integer>& args) {
|
|
ContextifyContext* ctx =
|
|
Unwrap<ContextifyContext>(args.Data().As<Object>());
|
|
|
|
// Stil initializing
|
|
if (ctx->context_.IsEmpty())
|
|
return;
|
|
|
|
Local<Context> context = ctx->context();
|
|
Maybe<PropertyAttribute> maybe_prop_attr =
|
|
ctx->sandbox()->GetRealNamedPropertyAttributes(context, property);
|
|
|
|
if (maybe_prop_attr.IsNothing()) {
|
|
maybe_prop_attr =
|
|
ctx->global_proxy()->GetRealNamedPropertyAttributes(context,
|
|
property);
|
|
}
|
|
|
|
if (maybe_prop_attr.IsJust()) {
|
|
PropertyAttribute prop_attr = maybe_prop_attr.FromJust();
|
|
args.GetReturnValue().Set(prop_attr);
|
|
}
|
|
}
|
|
|
|
|
|
static void GlobalPropertyDeleterCallback(
|
|
Local<Name> property,
|
|
const PropertyCallbackInfo<Boolean>& args) {
|
|
ContextifyContext* ctx =
|
|
Unwrap<ContextifyContext>(args.Data().As<Object>());
|
|
|
|
// Stil initializing
|
|
if (ctx->context_.IsEmpty())
|
|
return;
|
|
|
|
Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), property);
|
|
|
|
if (success.IsJust())
|
|
args.GetReturnValue().Set(success.FromJust());
|
|
}
|
|
|
|
|
|
static void GlobalPropertyEnumeratorCallback(
|
|
const PropertyCallbackInfo<Array>& args) {
|
|
ContextifyContext* ctx =
|
|
Unwrap<ContextifyContext>(args.Data().As<Object>());
|
|
|
|
// Stil initializing
|
|
if (ctx->context_.IsEmpty())
|
|
return;
|
|
|
|
args.GetReturnValue().Set(ctx->sandbox()->GetPropertyNames());
|
|
}
|
|
};
|
|
|
|
class ContextifyScript : public BaseObject {
|
|
private:
|
|
Persistent<UnboundScript> script_;
|
|
|
|
public:
|
|
static void Init(Environment* env, Local<Object> target) {
|
|
HandleScope scope(env->isolate());
|
|
Local<String> class_name =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
|
|
|
|
Local<FunctionTemplate> script_tmpl = env->NewFunctionTemplate(New);
|
|
script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
script_tmpl->SetClassName(class_name);
|
|
env->SetProtoMethod(script_tmpl, "runInContext", RunInContext);
|
|
env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
|
|
|
|
target->Set(class_name, script_tmpl->GetFunction());
|
|
env->set_script_context_constructor_template(script_tmpl);
|
|
}
|
|
|
|
|
|
// args: code, [options]
|
|
static void New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (!args.IsConstructCall()) {
|
|
return env->ThrowError("Must call vm.Script as a constructor.");
|
|
}
|
|
|
|
ContextifyScript* contextify_script =
|
|
new ContextifyScript(env, args.This());
|
|
|
|
TryCatch try_catch(env->isolate());
|
|
Local<String> code = args[0]->ToString(env->isolate());
|
|
Local<String> filename = GetFilenameArg(args, 1);
|
|
Local<Integer> lineOffset = GetLineOffsetArg(args, 1);
|
|
Local<Integer> columnOffset = GetColumnOffsetArg(args, 1);
|
|
bool display_errors = GetDisplayErrorsArg(args, 1);
|
|
MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, args, 1);
|
|
bool produce_cached_data = GetProduceCachedData(env, args, 1);
|
|
if (try_catch.HasCaught()) {
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
|
|
ScriptCompiler::CachedData* cached_data = nullptr;
|
|
if (!cached_data_buf.IsEmpty()) {
|
|
Local<Uint8Array> ui8 = cached_data_buf.ToLocalChecked();
|
|
ArrayBuffer::Contents contents = ui8->Buffer()->GetContents();
|
|
cached_data = new ScriptCompiler::CachedData(
|
|
static_cast<uint8_t*>(contents.Data()) + ui8->ByteOffset(),
|
|
ui8->ByteLength());
|
|
}
|
|
|
|
ScriptOrigin origin(filename, lineOffset, columnOffset);
|
|
ScriptCompiler::Source source(code, origin, cached_data);
|
|
ScriptCompiler::CompileOptions compile_options =
|
|
ScriptCompiler::kNoCompileOptions;
|
|
|
|
if (source.GetCachedData() != nullptr)
|
|
compile_options = ScriptCompiler::kConsumeCodeCache;
|
|
else if (produce_cached_data)
|
|
compile_options = ScriptCompiler::kProduceCodeCache;
|
|
|
|
MaybeLocal<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
|
|
env->isolate(),
|
|
&source,
|
|
compile_options);
|
|
|
|
if (v8_script.IsEmpty()) {
|
|
if (display_errors) {
|
|
DecorateErrorStack(env, try_catch);
|
|
}
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
contextify_script->script_.Reset(env->isolate(),
|
|
v8_script.ToLocalChecked());
|
|
|
|
if (compile_options == ScriptCompiler::kConsumeCodeCache) {
|
|
args.This()->Set(
|
|
env->cached_data_rejected_string(),
|
|
Boolean::New(env->isolate(), source.GetCachedData()->rejected));
|
|
} else if (compile_options == ScriptCompiler::kProduceCodeCache) {
|
|
const ScriptCompiler::CachedData* cached_data = source.GetCachedData();
|
|
bool cached_data_produced = cached_data != nullptr;
|
|
if (cached_data_produced) {
|
|
MaybeLocal<Object> buf = Buffer::Copy(
|
|
env,
|
|
reinterpret_cast<const char*>(cached_data->data),
|
|
cached_data->length);
|
|
args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
|
|
}
|
|
args.This()->Set(
|
|
env->cached_data_produced_string(),
|
|
Boolean::New(env->isolate(), cached_data_produced));
|
|
}
|
|
}
|
|
|
|
|
|
static bool InstanceOf(Environment* env, const Local<Value>& value) {
|
|
return !value.IsEmpty() &&
|
|
env->script_context_constructor_template()->HasInstance(value);
|
|
}
|
|
|
|
|
|
// args: [options]
|
|
static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
|
|
// Assemble arguments
|
|
TryCatch try_catch(args.GetIsolate());
|
|
uint64_t timeout = GetTimeoutArg(args, 0);
|
|
bool display_errors = GetDisplayErrorsArg(args, 0);
|
|
if (try_catch.HasCaught()) {
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
|
|
// Do the eval within this context
|
|
Environment* env = Environment::GetCurrent(args);
|
|
EvalMachine(env, timeout, display_errors, args, try_catch);
|
|
}
|
|
|
|
// args: sandbox, [options]
|
|
static void RunInContext(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
int64_t timeout;
|
|
bool display_errors;
|
|
|
|
// Assemble arguments
|
|
if (!args[0]->IsObject()) {
|
|
return env->ThrowTypeError(
|
|
"contextifiedSandbox argument must be an object.");
|
|
}
|
|
|
|
Local<Object> sandbox = args[0].As<Object>();
|
|
{
|
|
TryCatch try_catch(env->isolate());
|
|
timeout = GetTimeoutArg(args, 1);
|
|
display_errors = GetDisplayErrorsArg(args, 1);
|
|
if (try_catch.HasCaught()) {
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the context from the sandbox
|
|
ContextifyContext* contextify_context =
|
|
ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
|
|
if (contextify_context == nullptr) {
|
|
return env->ThrowTypeError(
|
|
"sandbox argument must have been converted to a context.");
|
|
}
|
|
|
|
if (contextify_context->context().IsEmpty())
|
|
return;
|
|
|
|
{
|
|
TryCatch try_catch(env->isolate());
|
|
// Do the eval within the context
|
|
Context::Scope context_scope(contextify_context->context());
|
|
if (EvalMachine(contextify_context->env(),
|
|
timeout,
|
|
display_errors,
|
|
args,
|
|
try_catch)) {
|
|
contextify_context->CopyProperties();
|
|
}
|
|
|
|
if (try_catch.HasCaught()) {
|
|
try_catch.ReThrow();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
|
|
Local<Value> exception = try_catch.Exception();
|
|
|
|
if (!exception->IsObject())
|
|
return;
|
|
|
|
Local<Object> err_obj = exception.As<Object>();
|
|
|
|
if (IsExceptionDecorated(env, err_obj))
|
|
return;
|
|
|
|
AppendExceptionLine(env, exception, try_catch.Message());
|
|
Local<Value> stack = err_obj->Get(env->stack_string());
|
|
auto maybe_value =
|
|
err_obj->GetPrivate(
|
|
env->context(),
|
|
env->arrow_message_private_symbol());
|
|
|
|
Local<Value> arrow;
|
|
if (!(maybe_value.ToLocal(&arrow) &&
|
|
arrow->IsString() &&
|
|
stack->IsString())) {
|
|
return;
|
|
}
|
|
|
|
Local<String> decorated_stack = String::Concat(arrow.As<String>(),
|
|
stack.As<String>());
|
|
err_obj->Set(env->stack_string(), decorated_stack);
|
|
err_obj->SetPrivate(
|
|
env->context(),
|
|
env->decorated_private_symbol(),
|
|
True(env->isolate()));
|
|
}
|
|
|
|
static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
if (args[i]->IsUndefined() || args[i]->IsString()) {
|
|
return -1;
|
|
}
|
|
if (!args[i]->IsObject()) {
|
|
Environment::ThrowTypeError(args.GetIsolate(),
|
|
"options must be an object");
|
|
return -1;
|
|
}
|
|
|
|
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout");
|
|
Local<Value> value = args[i].As<Object>()->Get(key);
|
|
if (value->IsUndefined()) {
|
|
return -1;
|
|
}
|
|
int64_t timeout = value->IntegerValue();
|
|
|
|
if (timeout <= 0) {
|
|
Environment::ThrowRangeError(args.GetIsolate(),
|
|
"timeout must be a positive number");
|
|
return -1;
|
|
}
|
|
return timeout;
|
|
}
|
|
|
|
|
|
static bool GetDisplayErrorsArg(const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
if (args[i]->IsUndefined() || args[i]->IsString()) {
|
|
return true;
|
|
}
|
|
if (!args[i]->IsObject()) {
|
|
Environment::ThrowTypeError(args.GetIsolate(),
|
|
"options must be an object");
|
|
return false;
|
|
}
|
|
|
|
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
|
|
"displayErrors");
|
|
Local<Value> value = args[i].As<Object>()->Get(key);
|
|
|
|
return value->IsUndefined() ? true : value->BooleanValue();
|
|
}
|
|
|
|
|
|
static Local<String> GetFilenameArg(const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
Local<String> defaultFilename =
|
|
FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine.<anonymous>");
|
|
|
|
if (args[i]->IsUndefined()) {
|
|
return defaultFilename;
|
|
}
|
|
if (args[i]->IsString()) {
|
|
return args[i].As<String>();
|
|
}
|
|
if (!args[i]->IsObject()) {
|
|
Environment::ThrowTypeError(args.GetIsolate(),
|
|
"options must be an object");
|
|
return Local<String>();
|
|
}
|
|
|
|
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename");
|
|
Local<Value> value = args[i].As<Object>()->Get(key);
|
|
|
|
if (value->IsUndefined())
|
|
return defaultFilename;
|
|
return value->ToString(args.GetIsolate());
|
|
}
|
|
|
|
|
|
static MaybeLocal<Uint8Array> GetCachedData(
|
|
Environment* env,
|
|
const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
if (!args[i]->IsObject()) {
|
|
return MaybeLocal<Uint8Array>();
|
|
}
|
|
Local<Value> value = args[i].As<Object>()->Get(env->cached_data_string());
|
|
if (value->IsUndefined()) {
|
|
return MaybeLocal<Uint8Array>();
|
|
}
|
|
|
|
if (!value->IsUint8Array()) {
|
|
Environment::ThrowTypeError(
|
|
args.GetIsolate(),
|
|
"options.cachedData must be a Buffer instance");
|
|
return MaybeLocal<Uint8Array>();
|
|
}
|
|
|
|
return value.As<Uint8Array>();
|
|
}
|
|
|
|
|
|
static bool GetProduceCachedData(
|
|
Environment* env,
|
|
const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
if (!args[i]->IsObject()) {
|
|
return false;
|
|
}
|
|
Local<Value> value =
|
|
args[i].As<Object>()->Get(env->produce_cached_data_string());
|
|
|
|
return value->IsTrue();
|
|
}
|
|
|
|
|
|
static Local<Integer> GetLineOffsetArg(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
Local<Integer> defaultLineOffset = Integer::New(args.GetIsolate(), 0);
|
|
|
|
if (!args[i]->IsObject()) {
|
|
return defaultLineOffset;
|
|
}
|
|
|
|
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset");
|
|
Local<Value> value = args[i].As<Object>()->Get(key);
|
|
|
|
return value->IsUndefined() ? defaultLineOffset : value->ToInteger();
|
|
}
|
|
|
|
|
|
static Local<Integer> GetColumnOffsetArg(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
const int i) {
|
|
Local<Integer> defaultColumnOffset = Integer::New(args.GetIsolate(), 0);
|
|
|
|
if (!args[i]->IsObject()) {
|
|
return defaultColumnOffset;
|
|
}
|
|
|
|
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
|
|
"columnOffset");
|
|
Local<Value> value = args[i].As<Object>()->Get(key);
|
|
|
|
return value->IsUndefined() ? defaultColumnOffset : value->ToInteger();
|
|
}
|
|
|
|
|
|
static bool EvalMachine(Environment* env,
|
|
const int64_t timeout,
|
|
const bool display_errors,
|
|
const FunctionCallbackInfo<Value>& args,
|
|
TryCatch& try_catch) {
|
|
if (!ContextifyScript::InstanceOf(env, args.Holder())) {
|
|
env->ThrowTypeError(
|
|
"Script methods can only be called on script instances.");
|
|
return false;
|
|
}
|
|
|
|
ContextifyScript* wrapped_script = Unwrap<ContextifyScript>(args.Holder());
|
|
Local<UnboundScript> unbound_script =
|
|
PersistentToLocal(env->isolate(), wrapped_script->script_);
|
|
Local<Script> script = unbound_script->BindToCurrentContext();
|
|
|
|
Local<Value> result;
|
|
if (timeout != -1) {
|
|
Watchdog wd(env->isolate(), timeout);
|
|
result = script->Run();
|
|
} else {
|
|
result = script->Run();
|
|
}
|
|
|
|
if (try_catch.HasCaught() && try_catch.HasTerminated()) {
|
|
env->isolate()->CancelTerminateExecution();
|
|
env->ThrowError("Script execution timed out.");
|
|
try_catch.ReThrow();
|
|
return false;
|
|
}
|
|
|
|
if (result.IsEmpty()) {
|
|
// Error occurred during execution of the script.
|
|
if (display_errors) {
|
|
DecorateErrorStack(env, try_catch);
|
|
}
|
|
try_catch.ReThrow();
|
|
return false;
|
|
}
|
|
|
|
args.GetReturnValue().Set(result);
|
|
return true;
|
|
}
|
|
|
|
|
|
ContextifyScript(Environment* env, Local<Object> object)
|
|
: BaseObject(env, object) {
|
|
MakeWeak<ContextifyScript>(this);
|
|
}
|
|
|
|
|
|
~ContextifyScript() override {
|
|
script_.Reset();
|
|
}
|
|
};
|
|
|
|
|
|
void InitContextify(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
ContextifyContext::Init(env, target);
|
|
ContextifyScript::Init(env, target);
|
|
}
|
|
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify);
|