Skip to content

Commit 1abbe0a

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 1a25f96 commit 1abbe0a

File tree

5 files changed

+405
-0
lines changed

5 files changed

+405
-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

+94
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ const {
2626
ContextifyScript,
2727
makeContext,
2828
isContext: _isContext,
29+
compileFunction: _compileFunction
2930
} = internalBinding('contextify');
31+
3032
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
3133
const { isUint8Array } = require('internal/util/types');
3234
const { validateInt32, validateUint32 } = require('internal/validators');
3335
const kParsingContext = Symbol('script parsing context');
3436

37+
const ArrayForEach = Function.call.bind(Array.prototype.forEach);
38+
const ArrayIsArray = Array.isArray;
39+
3540
class Script extends ContextifyScript {
3641
constructor(code, options = {}) {
3742
code = `${code}`;
@@ -286,6 +291,94 @@ function runInThisContext(code, options) {
286291
return createScript(code, options).runInThisContext(options);
287292
}
288293

294+
function compileFunction(code, params, options = {}) {
295+
if (typeof code !== 'string') {
296+
throw new ERR_INVALID_ARG_TYPE('code', 'string', code);
297+
}
298+
if (params !== undefined) {
299+
if (!ArrayIsArray(params)) {
300+
throw new ERR_INVALID_ARG_TYPE('params', 'Array', params);
301+
}
302+
ArrayForEach(params, (param, i) => {
303+
if (typeof param !== 'string') {
304+
throw new ERR_INVALID_ARG_TYPE(`params[${i}]`, 'string', param);
305+
}
306+
});
307+
}
308+
309+
const {
310+
filename = '',
311+
columnOffset = 0,
312+
lineOffset = 0,
313+
cachedData = undefined,
314+
produceCachedData = false,
315+
parsingContext = undefined,
316+
contextExtensions = [],
317+
} = options;
318+
319+
if (typeof filename !== 'string') {
320+
throw new ERR_INVALID_ARG_TYPE('options.filename', 'string', filename);
321+
}
322+
validateUint32(columnOffset, 'options.columnOffset');
323+
validateUint32(lineOffset, 'options.lineOffset');
324+
if (cachedData !== undefined && !isUint8Array(cachedData)) {
325+
throw new ERR_INVALID_ARG_TYPE(
326+
'options.cachedData',
327+
'Uint8Array',
328+
cachedData
329+
);
330+
}
331+
if (typeof produceCachedData !== 'boolean') {
332+
throw new ERR_INVALID_ARG_TYPE(
333+
'options.produceCachedData',
334+
'boolean',
335+
produceCachedData
336+
);
337+
}
338+
if (parsingContext !== undefined) {
339+
if (
340+
typeof parsingContext !== 'object' ||
341+
parsingContext === null ||
342+
!isContext(parsingContext)
343+
) {
344+
throw new ERR_INVALID_ARG_TYPE(
345+
'options.parsingContext',
346+
'Context',
347+
parsingContext
348+
);
349+
}
350+
}
351+
if (!ArrayIsArray(contextExtensions)) {
352+
throw new ERR_INVALID_ARG_TYPE(
353+
'options.contextExtensions',
354+
'Array',
355+
contextExtensions
356+
);
357+
}
358+
ArrayForEach(contextExtensions, (extension, i) => {
359+
if (typeof extension !== 'object') {
360+
throw new ERR_INVALID_ARG_TYPE(
361+
`options.contextExtensions[${i}]`,
362+
'object',
363+
extension
364+
);
365+
}
366+
});
367+
368+
return _compileFunction(
369+
code,
370+
filename,
371+
lineOffset,
372+
columnOffset,
373+
cachedData,
374+
produceCachedData,
375+
parsingContext,
376+
contextExtensions,
377+
params
378+
);
379+
}
380+
381+
289382
module.exports = {
290383
Script,
291384
createContext,
@@ -294,6 +387,7 @@ module.exports = {
294387
runInNewContext,
295388
runInThisContext,
296389
isContext,
390+
compileFunction,
297391
};
298392

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

src/node_contextify.cc

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

210210
env->SetMethod(target, "makeContext", MakeContext);
211211
env->SetMethod(target, "isContext", IsContext);
212+
env->SetMethod(target, "compileFunction", CompileFunction);
212213
}
213214

214215

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

989990

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