Skip to content

Commit 2937a79

Browse files
ryzokukentargos
authored andcommitted
vm: add bindings for v8::CompileFunctionInContext
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 <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: John-David Dalton <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent e24cd92 commit 2937a79

File tree

5 files changed

+406
-0
lines changed

5 files changed

+406
-0
lines changed

doc/api/vm.md

+28
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,34 @@ console.log(globalVar);
637637
// 1000
638638
```
639639

640+
## vm.compileFunction(code[, params[, options]])
641+
<!-- YAML
642+
added: REPLACEME
643+
-->
644+
* `code` {string} The body of the function to compile.
645+
* `params` {string[]} An array of strings containing all parameters for the
646+
function.
647+
* `options` {Object}
648+
* `filename` {string} Specifies the filename used in stack traces produced
649+
by this script. **Default:** `''`.
650+
* `lineOffset` {number} Specifies the line number offset that is displayed
651+
in stack traces produced by this script. **Default:** `0`.
652+
* `columnOffset` {number} Specifies the column number offset that is displayed
653+
in stack traces produced by this script. **Default:** `0`.
654+
* `cachedData` {Buffer} Provides an optional `Buffer` with V8's code cache
655+
data for the supplied source.
656+
* `produceCachedData` {boolean} Specifies whether to produce new cache data.
657+
**Default:** `false`.
658+
* `parsingContext` {Object} The sandbox/context in which the said function
659+
should be compiled in.
660+
* `contextExtensions` {Object[]} An array containing a collection of context
661+
extensions (objects wrapping the current scope) to be applied while
662+
compiling. **Default:** `[]`.
663+
664+
Compiles the given code into the provided context/sandbox (if no context is
665+
supplied, the current context is used), and returns it wrapped inside a
666+
function with the given `params`.
667+
640668
## vm.createContext([sandbox[, options]])
641669
<!-- YAML
642670
added: v0.3.1

lib/vm.js

+95
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,20 @@ const {
2525
ContextifyScript,
2626
makeContext,
2727
isContext: _isContext,
28+
compileFunction: _compileFunction
2829
} = process.binding('contextify');
30+
2931
const {
3032
ERR_INVALID_ARG_TYPE,
3133
ERR_OUT_OF_RANGE
3234
} = require('internal/errors').codes;
3335
const { isUint8Array } = require('internal/util/types');
36+
const { validateUint32 } = require('internal/validators');
3437
const kParsingContext = Symbol('script parsing context');
3538

39+
const ArrayForEach = Function.call.bind(Array.prototype.forEach);
40+
const ArrayIsArray = Array.isArray;
41+
3642
class Script extends ContextifyScript {
3743
constructor(code, options = {}) {
3844
code = `${code}`;
@@ -297,6 +303,94 @@ function runInThisContext(code, options) {
297303
return createScript(code, options).runInThisContext(options);
298304
}
299305

306+
function compileFunction(code, params, options = {}) {
307+
if (typeof code !== 'string') {
308+
throw new ERR_INVALID_ARG_TYPE('code', 'string', code);
309+
}
310+
if (params !== undefined) {
311+
if (!ArrayIsArray(params)) {
312+
throw new ERR_INVALID_ARG_TYPE('params', 'Array', params);
313+
}
314+
ArrayForEach(params, (param, i) => {
315+
if (typeof param !== 'string') {
316+
throw new ERR_INVALID_ARG_TYPE(`params[${i}]`, 'string', param);
317+
}
318+
});
319+
}
320+
321+
const {
322+
filename = '',
323+
columnOffset = 0,
324+
lineOffset = 0,
325+
cachedData = undefined,
326+
produceCachedData = false,
327+
parsingContext = undefined,
328+
contextExtensions = [],
329+
} = options;
330+
331+
if (typeof filename !== 'string') {
332+
throw new ERR_INVALID_ARG_TYPE('options.filename', 'string', filename);
333+
}
334+
validateUint32(columnOffset, 'options.columnOffset');
335+
validateUint32(lineOffset, 'options.lineOffset');
336+
if (cachedData !== undefined && !isUint8Array(cachedData)) {
337+
throw new ERR_INVALID_ARG_TYPE(
338+
'options.cachedData',
339+
'Uint8Array',
340+
cachedData
341+
);
342+
}
343+
if (typeof produceCachedData !== 'boolean') {
344+
throw new ERR_INVALID_ARG_TYPE(
345+
'options.produceCachedData',
346+
'boolean',
347+
produceCachedData
348+
);
349+
}
350+
if (parsingContext !== undefined) {
351+
if (
352+
typeof parsingContext !== 'object' ||
353+
parsingContext === null ||
354+
!isContext(parsingContext)
355+
) {
356+
throw new ERR_INVALID_ARG_TYPE(
357+
'options.parsingContext',
358+
'Context',
359+
parsingContext
360+
);
361+
}
362+
}
363+
if (!ArrayIsArray(contextExtensions)) {
364+
throw new ERR_INVALID_ARG_TYPE(
365+
'options.contextExtensions',
366+
'Array',
367+
contextExtensions
368+
);
369+
}
370+
ArrayForEach(contextExtensions, (extension, i) => {
371+
if (typeof extension !== 'object') {
372+
throw new ERR_INVALID_ARG_TYPE(
373+
`options.contextExtensions[${i}]`,
374+
'object',
375+
extension
376+
);
377+
}
378+
});
379+
380+
return _compileFunction(
381+
code,
382+
filename,
383+
lineOffset,
384+
columnOffset,
385+
cachedData,
386+
produceCachedData,
387+
parsingContext,
388+
contextExtensions,
389+
params
390+
);
391+
}
392+
393+
300394
module.exports = {
301395
Script,
302396
createContext,
@@ -305,6 +399,7 @@ module.exports = {
305399
runInNewContext,
306400
runInThisContext,
307401
isContext,
402+
compileFunction,
308403
};
309404

310405
if (process.binding('config').experimentalVMModules) {

src/node_contextify.cc

+139
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ void ContextifyContext::Init(Environment* env, Local<Object> target) {
208208

209209
env->SetMethod(target, "makeContext", MakeContext);
210210
env->SetMethod(target, "isContext", IsContext);
211+
env->SetMethod(target, "compileFunction", CompileFunction);
211212
}
212213

213214

@@ -986,6 +987,144 @@ class ContextifyScript : public BaseObject {
986987
};
987988

988989

990+
void ContextifyContext::CompileFunction(
991+
const FunctionCallbackInfo<Value>& args) {
992+
Environment* env = Environment::GetCurrent(args);
993+
Isolate* isolate = env->isolate();
994+
Local<Context> context = env->context();
995+
996+
// Argument 1: source code
997+
CHECK(args[0]->IsString());
998+
Local<String> code = args[0].As<String>();
999+
1000+
// Argument 2: filename
1001+
CHECK(args[1]->IsString());
1002+
Local<String> filename = args[1].As<String>();
1003+
1004+
// Argument 3: line offset
1005+
CHECK(args[2]->IsNumber());
1006+
Local<Integer> line_offset = args[2].As<Integer>();
1007+
1008+
// Argument 4: column offset
1009+
CHECK(args[3]->IsNumber());
1010+
Local<Integer> column_offset = args[3].As<Integer>();
1011+
1012+
// Argument 5: cached data (optional)
1013+
Local<Uint8Array> cached_data_buf;
1014+
if (!args[4]->IsUndefined()) {
1015+
CHECK(args[4]->IsUint8Array());
1016+
cached_data_buf = args[4].As<Uint8Array>();
1017+
}
1018+
1019+
// Argument 6: produce cache data
1020+
CHECK(args[5]->IsBoolean());
1021+
bool produce_cached_data = args[5]->IsTrue();
1022+
1023+
// Argument 7: parsing context (optional)
1024+
Local<Context> parsing_context;
1025+
if (!args[6]->IsUndefined()) {
1026+
CHECK(args[6]->IsObject());
1027+
ContextifyContext* sandbox =
1028+
ContextifyContext::ContextFromContextifiedSandbox(
1029+
env, args[6].As<Object>());
1030+
CHECK_NOT_NULL(sandbox);
1031+
parsing_context = sandbox->context();
1032+
} else {
1033+
parsing_context = context;
1034+
}
1035+
1036+
// Argument 8: context extensions (optional)
1037+
Local<Array> context_extensions_buf;
1038+
if (!args[7]->IsUndefined()) {
1039+
CHECK(args[7]->IsArray());
1040+
context_extensions_buf = args[7].As<Array>();
1041+
}
1042+
1043+
// Argument 9: params for the function (optional)
1044+
Local<Array> params_buf;
1045+
if (!args[8]->IsUndefined()) {
1046+
CHECK(args[8]->IsArray());
1047+
params_buf = args[8].As<Array>();
1048+
}
1049+
1050+
// Read cache from cached data buffer
1051+
ScriptCompiler::CachedData* cached_data = nullptr;
1052+
if (!cached_data_buf.IsEmpty()) {
1053+
ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents();
1054+
uint8_t* data = static_cast<uint8_t*>(contents.Data());
1055+
cached_data = new ScriptCompiler::CachedData(
1056+
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
1057+
}
1058+
1059+
ScriptOrigin origin(filename, line_offset, column_offset);
1060+
ScriptCompiler::Source source(code, origin, cached_data);
1061+
ScriptCompiler::CompileOptions options;
1062+
if (source.GetCachedData() == nullptr) {
1063+
options = ScriptCompiler::kNoCompileOptions;
1064+
} else {
1065+
options = ScriptCompiler::kConsumeCodeCache;
1066+
}
1067+
1068+
TryCatch try_catch(isolate);
1069+
Context::Scope scope(parsing_context);
1070+
1071+
// Read context extensions from buffer
1072+
std::vector<Local<Object>> context_extensions;
1073+
if (!context_extensions_buf.IsEmpty()) {
1074+
for (uint32_t n = 0; n < context_extensions_buf->Length(); n++) {
1075+
Local<Value> val;
1076+
if (!context_extensions_buf->Get(context, n).ToLocal(&val)) return;
1077+
CHECK(val->IsObject());
1078+
context_extensions.push_back(val.As<Object>());
1079+
}
1080+
}
1081+
1082+
// Read params from params buffer
1083+
std::vector<Local<String>> params;
1084+
if (!params_buf.IsEmpty()) {
1085+
for (uint32_t n = 0; n < params_buf->Length(); n++) {
1086+
Local<Value> val;
1087+
if (!params_buf->Get(context, n).ToLocal(&val)) return;
1088+
CHECK(val->IsString());
1089+
params.push_back(val.As<String>());
1090+
}
1091+
}
1092+
1093+
MaybeLocal<Function> maybe_fun = ScriptCompiler::CompileFunctionInContext(
1094+
context, &source, params.size(), params.data(),
1095+
context_extensions.size(), context_extensions.data(), options);
1096+
1097+
Local<Function> fun;
1098+
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
1099+
ContextifyScript::DecorateErrorStack(env, try_catch);
1100+
try_catch.ReThrow();
1101+
return;
1102+
}
1103+
1104+
if (produce_cached_data) {
1105+
const std::unique_ptr<ScriptCompiler::CachedData>
1106+
cached_data(ScriptCompiler::CreateCodeCacheForFunction(fun, code));
1107+
bool cached_data_produced = cached_data != nullptr;
1108+
if (cached_data_produced) {
1109+
MaybeLocal<Object> buf = Buffer::Copy(
1110+
env,
1111+
reinterpret_cast<const char*>(cached_data->data),
1112+
cached_data->length);
1113+
if (fun->Set(
1114+
parsing_context,
1115+
env->cached_data_string(),
1116+
buf.ToLocalChecked()).IsNothing()) return;
1117+
}
1118+
if (fun->Set(
1119+
parsing_context,
1120+
env->cached_data_produced_string(),
1121+
Boolean::New(isolate, cached_data_produced)).IsNothing()) return;
1122+
}
1123+
1124+
args.GetReturnValue().Set(fun);
1125+
}
1126+
1127+
9891128
void Initialize(Local<Object> target,
9901129
Local<Value> unused,
9911130
Local<Context> context) {

src/node_contextify.h

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class ContextifyContext {
5858
private:
5959
static void MakeContext(const v8::FunctionCallbackInfo<v8::Value>& args);
6060
static void IsContext(const v8::FunctionCallbackInfo<v8::Value>& args);
61+
static void CompileFunction(
62+
const v8::FunctionCallbackInfo<v8::Value>& args);
6163
static void WeakCallback(
6264
const v8::WeakCallbackInfo<ContextifyContext>& data);
6365
static void PropertyGetterCallback(

0 commit comments

Comments
 (0)