Skip to content

Commit

Permalink
vm: add bindings for v8::CompileFunctionInContext
Browse files Browse the repository at this point in the history
Adds a method compileFunction to the vm module, which serves as a
binding for v8::CompileFunctionInContext with appropriate args for
specifying the details, and provide params for the wrapper.

Eventually, we would be changing Module._compile to use this internally
over the standard Module.wrap

PR-URL: #21571
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
ryzokuken authored and targos committed Sep 3, 2018
1 parent e24cd92 commit 2937a79
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 0 deletions.
28 changes: 28 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,34 @@ console.log(globalVar);
// 1000
```

## vm.compileFunction(code[, params[, options]])
<!-- YAML
added: REPLACEME
-->
* `code` {string} The body of the function to compile.
* `params` {string[]} An array of strings containing all parameters for the
function.
* `options` {Object}
* `filename` {string} Specifies the filename used in stack traces produced
by this script. **Default:** `''`.
* `lineOffset` {number} Specifies the line number offset that is displayed
in stack traces produced by this script. **Default:** `0`.
* `columnOffset` {number} Specifies the column number offset that is displayed
in stack traces produced by this script. **Default:** `0`.
* `cachedData` {Buffer} Provides an optional `Buffer` with V8's code cache
data for the supplied source.
* `produceCachedData` {boolean} Specifies whether to produce new cache data.
**Default:** `false`.
* `parsingContext` {Object} The sandbox/context in which the said function
should be compiled in.
* `contextExtensions` {Object[]} An array containing a collection of context
extensions (objects wrapping the current scope) to be applied while
compiling. **Default:** `[]`.

Compiles the given code into the provided context/sandbox (if no context is
supplied, the current context is used), and returns it wrapped inside a
function with the given `params`.

## vm.createContext([sandbox[, options]])
<!-- YAML
added: v0.3.1
Expand Down
95 changes: 95 additions & 0 deletions lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ const {
ContextifyScript,
makeContext,
isContext: _isContext,
compileFunction: _compileFunction
} = process.binding('contextify');

const {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE
} = require('internal/errors').codes;
const { isUint8Array } = require('internal/util/types');
const { validateUint32 } = require('internal/validators');
const kParsingContext = Symbol('script parsing context');

const ArrayForEach = Function.call.bind(Array.prototype.forEach);
const ArrayIsArray = Array.isArray;

class Script extends ContextifyScript {
constructor(code, options = {}) {
code = `${code}`;
Expand Down Expand Up @@ -297,6 +303,94 @@ function runInThisContext(code, options) {
return createScript(code, options).runInThisContext(options);
}

function compileFunction(code, params, options = {}) {
if (typeof code !== 'string') {
throw new ERR_INVALID_ARG_TYPE('code', 'string', code);
}
if (params !== undefined) {
if (!ArrayIsArray(params)) {
throw new ERR_INVALID_ARG_TYPE('params', 'Array', params);
}
ArrayForEach(params, (param, i) => {
if (typeof param !== 'string') {
throw new ERR_INVALID_ARG_TYPE(`params[${i}]`, 'string', param);
}
});
}

const {
filename = '',
columnOffset = 0,
lineOffset = 0,
cachedData = undefined,
produceCachedData = false,
parsingContext = undefined,
contextExtensions = [],
} = options;

if (typeof filename !== 'string') {
throw new ERR_INVALID_ARG_TYPE('options.filename', 'string', filename);
}
validateUint32(columnOffset, 'options.columnOffset');
validateUint32(lineOffset, 'options.lineOffset');
if (cachedData !== undefined && !isUint8Array(cachedData)) {
throw new ERR_INVALID_ARG_TYPE(
'options.cachedData',
'Uint8Array',
cachedData
);
}
if (typeof produceCachedData !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE(
'options.produceCachedData',
'boolean',
produceCachedData
);
}
if (parsingContext !== undefined) {
if (
typeof parsingContext !== 'object' ||
parsingContext === null ||
!isContext(parsingContext)
) {
throw new ERR_INVALID_ARG_TYPE(
'options.parsingContext',
'Context',
parsingContext
);
}
}
if (!ArrayIsArray(contextExtensions)) {
throw new ERR_INVALID_ARG_TYPE(
'options.contextExtensions',
'Array',
contextExtensions
);
}
ArrayForEach(contextExtensions, (extension, i) => {
if (typeof extension !== 'object') {
throw new ERR_INVALID_ARG_TYPE(
`options.contextExtensions[${i}]`,
'object',
extension
);
}
});

return _compileFunction(
code,
filename,
lineOffset,
columnOffset,
cachedData,
produceCachedData,
parsingContext,
contextExtensions,
params
);
}


module.exports = {
Script,
createContext,
Expand All @@ -305,6 +399,7 @@ module.exports = {
runInNewContext,
runInThisContext,
isContext,
compileFunction,
};

if (process.binding('config').experimentalVMModules) {
Expand Down
139 changes: 139 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ void ContextifyContext::Init(Environment* env, Local<Object> target) {

env->SetMethod(target, "makeContext", MakeContext);
env->SetMethod(target, "isContext", IsContext);
env->SetMethod(target, "compileFunction", CompileFunction);
}


Expand Down Expand Up @@ -986,6 +987,144 @@ class ContextifyScript : public BaseObject {
};


void ContextifyContext::CompileFunction(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
Local<Context> context = env->context();

// Argument 1: source code
CHECK(args[0]->IsString());
Local<String> code = args[0].As<String>();

// Argument 2: filename
CHECK(args[1]->IsString());
Local<String> filename = args[1].As<String>();

// Argument 3: line offset
CHECK(args[2]->IsNumber());
Local<Integer> line_offset = args[2].As<Integer>();

// Argument 4: column offset
CHECK(args[3]->IsNumber());
Local<Integer> column_offset = args[3].As<Integer>();

// Argument 5: cached data (optional)
Local<Uint8Array> cached_data_buf;
if (!args[4]->IsUndefined()) {
CHECK(args[4]->IsUint8Array());
cached_data_buf = args[4].As<Uint8Array>();
}

// Argument 6: produce cache data
CHECK(args[5]->IsBoolean());
bool produce_cached_data = args[5]->IsTrue();

// Argument 7: parsing context (optional)
Local<Context> parsing_context;
if (!args[6]->IsUndefined()) {
CHECK(args[6]->IsObject());
ContextifyContext* sandbox =
ContextifyContext::ContextFromContextifiedSandbox(
env, args[6].As<Object>());
CHECK_NOT_NULL(sandbox);
parsing_context = sandbox->context();
} else {
parsing_context = context;
}

// Argument 8: context extensions (optional)
Local<Array> context_extensions_buf;
if (!args[7]->IsUndefined()) {
CHECK(args[7]->IsArray());
context_extensions_buf = args[7].As<Array>();
}

// Argument 9: params for the function (optional)
Local<Array> params_buf;
if (!args[8]->IsUndefined()) {
CHECK(args[8]->IsArray());
params_buf = args[8].As<Array>();
}

// Read cache from cached data buffer
ScriptCompiler::CachedData* cached_data = nullptr;
if (!cached_data_buf.IsEmpty()) {
ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents();
uint8_t* data = static_cast<uint8_t*>(contents.Data());
cached_data = new ScriptCompiler::CachedData(
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
}

ScriptOrigin origin(filename, line_offset, column_offset);
ScriptCompiler::Source source(code, origin, cached_data);
ScriptCompiler::CompileOptions options;
if (source.GetCachedData() == nullptr) {
options = ScriptCompiler::kNoCompileOptions;
} else {
options = ScriptCompiler::kConsumeCodeCache;
}

TryCatch try_catch(isolate);
Context::Scope scope(parsing_context);

// Read context extensions from buffer
std::vector<Local<Object>> context_extensions;
if (!context_extensions_buf.IsEmpty()) {
for (uint32_t n = 0; n < context_extensions_buf->Length(); n++) {
Local<Value> val;
if (!context_extensions_buf->Get(context, n).ToLocal(&val)) return;
CHECK(val->IsObject());
context_extensions.push_back(val.As<Object>());
}
}

// Read params from params buffer
std::vector<Local<String>> params;
if (!params_buf.IsEmpty()) {
for (uint32_t n = 0; n < params_buf->Length(); n++) {
Local<Value> val;
if (!params_buf->Get(context, n).ToLocal(&val)) return;
CHECK(val->IsString());
params.push_back(val.As<String>());
}
}

MaybeLocal<Function> maybe_fun = ScriptCompiler::CompileFunctionInContext(
context, &source, params.size(), params.data(),
context_extensions.size(), context_extensions.data(), options);

Local<Function> fun;
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
ContextifyScript::DecorateErrorStack(env, try_catch);
try_catch.ReThrow();
return;
}

if (produce_cached_data) {
const std::unique_ptr<ScriptCompiler::CachedData>
cached_data(ScriptCompiler::CreateCodeCacheForFunction(fun, code));
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);
if (fun->Set(
parsing_context,
env->cached_data_string(),
buf.ToLocalChecked()).IsNothing()) return;
}
if (fun->Set(
parsing_context,
env->cached_data_produced_string(),
Boolean::New(isolate, cached_data_produced)).IsNothing()) return;
}

args.GetReturnValue().Set(fun);
}


void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
Expand Down
2 changes: 2 additions & 0 deletions src/node_contextify.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class ContextifyContext {
private:
static void MakeContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CompileFunction(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void WeakCallback(
const v8::WeakCallbackInfo<ContextifyContext>& data);
static void PropertyGetterCallback(
Expand Down
Loading

0 comments on commit 2937a79

Please sign in to comment.