Skip to content

Commit a0bab89

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 ca2dd8c commit a0bab89

File tree

5 files changed

+406
-0
lines changed

5 files changed

+406
-0
lines changed

doc/api/vm.md

Lines changed: 28 additions & 0 deletions
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

Lines changed: 95 additions & 0 deletions
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

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ void ContextifyContext::Init(Environment* env, Local<Object> target) {
206206

207207
env->SetMethod(target, "makeContext", MakeContext);
208208
env->SetMethod(target, "isContext", IsContext);
209+
env->SetMethod(target, "compileFunction", CompileFunction);
209210
}
210211

211212

@@ -948,6 +949,144 @@ class ContextifyScript : public BaseObject {
948949
};
949950

950951

952+
void ContextifyContext::CompileFunction(
953+
const FunctionCallbackInfo<Value>& args) {
954+
Environment* env = Environment::GetCurrent(args);
955+
Isolate* isolate = env->isolate();
956+
Local<Context> context = env->context();
957+
958+
// Argument 1: source code
959+
CHECK(args[0]->IsString());
960+
Local<String> code = args[0].As<String>();
961+
962+
// Argument 2: filename
963+
CHECK(args[1]->IsString());
964+
Local<String> filename = args[1].As<String>();
965+
966+
// Argument 3: line offset
967+
CHECK(args[2]->IsNumber());
968+
Local<Integer> line_offset = args[2].As<Integer>();
969+
970+
// Argument 4: column offset
971+
CHECK(args[3]->IsNumber());
972+
Local<Integer> column_offset = args[3].As<Integer>();
973+
974+
// Argument 5: cached data (optional)
975+
Local<Uint8Array> cached_data_buf;
976+
if (!args[4]->IsUndefined()) {
977+
CHECK(args[4]->IsUint8Array());
978+
cached_data_buf = args[4].As<Uint8Array>();
979+
}
980+
981+
// Argument 6: produce cache data
982+
CHECK(args[5]->IsBoolean());
983+
bool produce_cached_data = args[5]->IsTrue();
984+
985+
// Argument 7: parsing context (optional)
986+
Local<Context> parsing_context;
987+
if (!args[6]->IsUndefined()) {
988+
CHECK(args[6]->IsObject());
989+
ContextifyContext* sandbox =
990+
ContextifyContext::ContextFromContextifiedSandbox(
991+
env, args[6].As<Object>());
992+
CHECK_NOT_NULL(sandbox);
993+
parsing_context = sandbox->context();
994+
} else {
995+
parsing_context = context;
996+
}
997+
998+
// Argument 8: context extensions (optional)
999+
Local<Array> context_extensions_buf;
1000+
if (!args[7]->IsUndefined()) {
1001+
CHECK(args[7]->IsArray());
1002+
context_extensions_buf = args[7].As<Array>();
1003+
}
1004+
1005+
// Argument 9: params for the function (optional)
1006+
Local<Array> params_buf;
1007+
if (!args[8]->IsUndefined()) {
1008+
CHECK(args[8]->IsArray());
1009+
params_buf = args[8].As<Array>();
1010+
}
1011+
1012+
// Read cache from cached data buffer
1013+
ScriptCompiler::CachedData* cached_data = nullptr;
1014+
if (!cached_data_buf.IsEmpty()) {
1015+
ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents();
1016+
uint8_t* data = static_cast<uint8_t*>(contents.Data());
1017+
cached_data = new ScriptCompiler::CachedData(
1018+
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
1019+
}
1020+
1021+
ScriptOrigin origin(filename, line_offset, column_offset);
1022+
ScriptCompiler::Source source(code, origin, cached_data);
1023+
ScriptCompiler::CompileOptions options;
1024+
if (source.GetCachedData() == nullptr) {
1025+
options = ScriptCompiler::kNoCompileOptions;
1026+
} else {
1027+
options = ScriptCompiler::kConsumeCodeCache;
1028+
}
1029+
1030+
TryCatch try_catch(isolate);
1031+
Context::Scope scope(parsing_context);
1032+
1033+
// Read context extensions from buffer
1034+
std::vector<Local<Object>> context_extensions;
1035+
if (!context_extensions_buf.IsEmpty()) {
1036+
for (uint32_t n = 0; n < context_extensions_buf->Length(); n++) {
1037+
Local<Value> val;
1038+
if (!context_extensions_buf->Get(context, n).ToLocal(&val)) return;
1039+
CHECK(val->IsObject());
1040+
context_extensions.push_back(val.As<Object>());
1041+
}
1042+
}
1043+
1044+
// Read params from params buffer
1045+
std::vector<Local<String>> params;
1046+
if (!params_buf.IsEmpty()) {
1047+
for (uint32_t n = 0; n < params_buf->Length(); n++) {
1048+
Local<Value> val;
1049+
if (!params_buf->Get(context, n).ToLocal(&val)) return;
1050+
CHECK(val->IsString());
1051+
params.push_back(val.As<String>());
1052+
}
1053+
}
1054+
1055+
MaybeLocal<Function> maybe_fun = ScriptCompiler::CompileFunctionInContext(
1056+
context, &source, params.size(), params.data(),
1057+
context_extensions.size(), context_extensions.data(), options);
1058+
1059+
Local<Function> fun;
1060+
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
1061+
ContextifyScript::DecorateErrorStack(env, try_catch);
1062+
try_catch.ReThrow();
1063+
return;
1064+
}
1065+
1066+
if (produce_cached_data) {
1067+
const std::unique_ptr<ScriptCompiler::CachedData>
1068+
cached_data(ScriptCompiler::CreateCodeCacheForFunction(fun, code));
1069+
bool cached_data_produced = cached_data != nullptr;
1070+
if (cached_data_produced) {
1071+
MaybeLocal<Object> buf = Buffer::Copy(
1072+
env,
1073+
reinterpret_cast<const char*>(cached_data->data),
1074+
cached_data->length);
1075+
if (fun->Set(
1076+
parsing_context,
1077+
env->cached_data_string(),
1078+
buf.ToLocalChecked()).IsNothing()) return;
1079+
}
1080+
if (fun->Set(
1081+
parsing_context,
1082+
env->cached_data_produced_string(),
1083+
Boolean::New(isolate, cached_data_produced)).IsNothing()) return;
1084+
}
1085+
1086+
args.GetReturnValue().Set(fun);
1087+
}
1088+
1089+
9511090
void Initialize(Local<Object> target,
9521091
Local<Value> unused,
9531092
Local<Context> context) {

src/node_contextify.h

Lines changed: 2 additions & 0 deletions
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)