Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v10.x-backport] module: use compileFunction over Module.wrap #27124

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ let isDeepEqual;
let isDeepStrictEqual;
let parseExpressionAt;
let findNodeAround;
let columnOffset = 0;
let decoder;

function lazyLoadComparison() {
Expand Down Expand Up @@ -255,16 +254,6 @@ function getErrMessage(message, fn) {
const line = call.getLineNumber() - 1;
let column = call.getColumnNumber() - 1;

// Line number one reports the wrong column due to being wrapped in a
// function. Remove that offset to get the actual call.
if (line === 0) {
if (columnOffset === 0) {
const { wrapper } = require('internal/modules/cjs/loader');
columnOffset = wrapper[0].length;
}
column -= columnOffset;
}

const identifier = `${filename}${line}${column}`;

if (errorCache.has(identifier)) {
Expand Down
11 changes: 11 additions & 0 deletions lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict';

const { validateString } = require('internal/validators');
const path = require('path');
const { pathToFileURL } = require('internal/url');
const { URL } = require('url');

const {
CHAR_LINE_FEED,
Expand Down Expand Up @@ -152,10 +155,18 @@ function addBuiltinLibsToObject(object) {
});
}

function normalizeReferrerURL(referrer) {
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
return pathToFileURL(referrer).href;
}
return new URL(referrer).href;
}

module.exports = exports = {
addBuiltinLibsToObject,
builtinLibs,
makeRequireFunction,
normalizeReferrerURL,
requireDepth: 0,
stripBOM,
stripShebang
Expand Down
106 changes: 83 additions & 23 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ const assert = require('assert').ok;
const fs = require('fs');
const internalFS = require('internal/fs/utils');
const path = require('path');
const { URL } = require('url');
const {
internalModuleReadJSON,
internalModuleStat
} = process.binding('fs');
const { safeGetenv } = process.binding('util');
const {
makeRequireFunction,
normalizeReferrerURL,
requireDepth,
stripBOM,
stripShebang
Expand All @@ -45,6 +45,7 @@ const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalModules = getOptionValue('--experimental-modules');
const { compileFunction } = internalBinding('contextify');

const {
ERR_INVALID_ARG_VALUE,
Expand Down Expand Up @@ -122,15 +123,52 @@ Module._extensions = Object.create(null);
var modulePaths = [];
Module.globalPaths = [];

Module.wrap = function(script) {
let patched = false;

// eslint-disable-next-line func-style
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};

Module.wrapper = [
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];

let wrapperProxy = new Proxy(wrapper, {
set(target, property, value, receiver) {
patched = true;
return Reflect.set(target, property, value, receiver);
},

defineProperty(target, property, descriptor) {
patched = true;
return Object.defineProperty(target, property, descriptor);
}
});

Object.defineProperty(Module, 'wrap', {
get() {
return wrap;
},

set(value) {
patched = true;
wrap = value;
}
});

Object.defineProperty(Module, 'wrapper', {
get() {
return wrapperProxy;
},

set(value) {
patched = true;
wrapperProxy = value;
}
});

const debug = util.debuglog('module');

Module._debug = util.deprecate(debug, 'Module._debug is deprecated.',
Expand Down Expand Up @@ -642,13 +680,6 @@ Module.prototype.require = function(id) {
// (needed for setting breakpoint when called with --inspect-brk)
var resolvedArgv;

function normalizeReferrerURL(referrer) {
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
return pathToFileURL(referrer).href;
}
return new URL(referrer).href;
}


// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
Expand All @@ -658,19 +689,48 @@ Module.prototype._compile = function(content, filename) {

content = stripShebang(content);

// create wrapper function
var wrapper = Module.wrap(content);

var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
importModuleDynamically: experimentalModules ? async (specifier) => {
if (asyncESM === undefined) lazyLoadESM();
const loader = await asyncESM.loaderPromise;
return loader.import(specifier, normalizeReferrerURL(filename));
} : undefined,
});
let compiledWrapper;
if (patched) {
const wrapper = Module.wrap(content);
compiledWrapper = vm.runInThisContext(wrapper, {
filename,
lineOffset: 0,
displayErrors: true,
importModuleDynamically: experimentalModules ? async (specifier) => {
if (asyncESM === undefined) lazyLoadESM();
const loader = await asyncESM.loaderPromise;
return loader.import(specifier, normalizeReferrerURL(filename));
} : undefined,
});
} else {
compiledWrapper = compileFunction(
content,
filename,
0,
0,
undefined,
false,
undefined,
[],
[
'exports',
'require',
'module',
'__filename',
'__dirname',
]
);
if (experimentalModules) {
const { callbackMap } = internalBinding('module_wrap');
callbackMap.set(compiledWrapper, {
importModuleDynamically: async (specifier) => {
if (asyncESM === undefined) lazyLoadESM();
const loader = await asyncESM.loaderPromise;
return loader.import(specifier, normalizeReferrerURL(filename));
}
});
}
}

var inspectorWrapper = null;
if (process._breakFirstLine && process._eval == null) {
Expand Down
3 changes: 3 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ inline uint32_t Environment::get_next_module_id() {
inline uint32_t Environment::get_next_script_id() {
return script_id_counter_++;
}
inline uint32_t Environment::get_next_function_id() {
return function_id_counter_++;
}

Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
Environment* env)
Expand Down
13 changes: 12 additions & 1 deletion src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ Environment::Environment(IsolateData* isolate_data,
}
}

CompileFnEntry::CompileFnEntry(Environment* env, uint32_t id)
: env(env), id(id) {
env->compile_fn_entries.insert(this);
}

Environment::~Environment() {
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
BuildEmbedderGraph, this);
Expand All @@ -174,7 +179,13 @@ Environment::~Environment() {
// CleanupHandles() should have removed all of them.
CHECK(file_handle_read_wrap_freelist_.empty());

v8::HandleScope handle_scope(isolate());
// dispose the Persistent references to the compileFunction
// wrappers used in the dynamic import callback
for (auto& entry : compile_fn_entries) {
delete entry;
}

HandleScope handle_scope(isolate());

#if HAVE_INSPECTOR
// Destroy inspector agent before erasing the context. The inspector
Expand Down
10 changes: 10 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ struct ContextInfo {
bool is_default = false;
};

struct CompileFnEntry {
Environment* env;
uint32_t id;
CompileFnEntry(Environment* env, uint32_t id);
};

// Listing the AsyncWrap provider types first enables us to cast directly
// from a provider type to a debug category.
#define DEBUG_CATEGORY_NAMES(V) \
Expand Down Expand Up @@ -679,9 +685,12 @@ class Environment {
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
std::unordered_map<uint32_t, contextify::ContextifyScript*>
id_to_script_map;
std::unordered_set<CompileFnEntry*> compile_fn_entries;
std::unordered_map<uint32_t, Persistent<v8::Function>> id_to_function_map;

inline uint32_t get_next_module_id();
inline uint32_t get_next_script_id();
inline uint32_t get_next_function_id();

std::unordered_map<std::string, const loader::PackageConfig>
package_json_cache;
Expand Down Expand Up @@ -911,6 +920,7 @@ class Environment {

uint32_t module_id_counter_ = 0;
uint32_t script_id_counter_ = 0;
uint32_t function_id_counter_ = 0;

AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
int should_not_abort_scope_counter_ = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ static MaybeLocal<Promise> ImportModuleDynamically(
} else if (type == ScriptType::kModule) {
ModuleWrap* wrap = ModuleWrap::GetFromID(env, id);
object = wrap->object();
} else if (type == ScriptType::kFunction) {
object = env->id_to_function_map.find(id)->second.Get(iso);
} else {
UNREACHABLE();
}
Expand Down
1 change: 1 addition & 0 deletions src/module_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum PackageMainCheck : bool {
enum ScriptType : int {
kScript,
kModule,
kFunction,
};

enum HostDefinedOptions : int {
Expand Down
55 changes: 47 additions & 8 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ void ContextifyContext::WeakCallback(
delete context;
}

void ContextifyContext::WeakCallbackCompileFn(
const WeakCallbackInfo<CompileFnEntry>& data) {
CompileFnEntry* entry = data.GetParameter();
if (entry->env->compile_fn_entries.erase(entry) != 0) {
entry->env->id_to_function_map.erase(entry->id);
delete entry;
}
}

// static
ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox(
Environment* env,
Expand Down Expand Up @@ -1039,7 +1048,30 @@ void ContextifyContext::CompileFunction(
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
}

ScriptOrigin origin(filename, line_offset, column_offset);
// Get the function id
uint32_t id = env->get_next_function_id();

// Set host_defined_options
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
host_defined_options->Set(
isolate,
loader::HostDefinedOptions::kType,
Number::New(isolate, loader::ScriptType::kFunction));
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, Number::New(isolate, id));

ScriptOrigin origin(filename,
line_offset, // line offset
column_offset, // column offset
True(isolate), // is cross origin
Local<Integer>(), // script id
Local<Value>(), // source map URL
False(isolate), // is opaque (?)
False(isolate), // is WASM
False(isolate), // is ES Module
host_defined_options);

ScriptCompiler::Source source(code, origin, cached_data);
ScriptCompiler::CompileOptions options;
if (source.GetCachedData() == nullptr) {
Expand Down Expand Up @@ -1073,38 +1105,45 @@ void ContextifyContext::CompileFunction(
}
}

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

Local<Function> fun;
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
if (maybe_fn.IsEmpty()) {
ContextifyScript::DecorateErrorStack(env, try_catch);
try_catch.ReThrow();
return;
}
Local<Function> fn = maybe_fn.ToLocalChecked();
env->id_to_function_map.emplace(std::piecewise_construct,
std::make_tuple(id),
std::make_tuple(isolate, fn));
CompileFnEntry* gc_entry = new CompileFnEntry(env, id);
env->id_to_function_map[id].SetWeak(gc_entry,
WeakCallbackCompileFn,
v8::WeakCallbackType::kParameter);

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

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


Expand Down
Loading