src: make a Environment-independent proxy class for NativeModuleLoader
This patch splits `NativeModuleLoader` into two parts - a singleton that only relies on v8 and `node::Mutex` and a proxy class for the singleton (`NativeModuleEnv`) that provides limited access to the singleton as well as C++ bindings for the Node.js binary. `NativeModuleLoader` is then no longer aware of `Environment`. PR-URL: https://github.com/nodejs/node/pull/27160 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Richard Lau <riclau@uk.ibm.com>
This commit is contained in:
parent
9b6b567bc4
commit
dfd7e99425
2
node.gyp
2
node.gyp
@ -463,6 +463,7 @@
|
||||
'src/node_messaging.cc',
|
||||
'src/node_metadata.cc',
|
||||
'src/node_native_module.cc',
|
||||
'src/node_native_module_env.cc',
|
||||
'src/node_options.cc',
|
||||
'src/node_os.cc',
|
||||
'src/node_perf.cc',
|
||||
@ -543,6 +544,7 @@
|
||||
'src/node_metadata.h',
|
||||
'src/node_mutex.h',
|
||||
'src/node_native_module.h',
|
||||
'src/node_native_module_env.h',
|
||||
'src/node_object_wrap.h',
|
||||
'src/node_options.h',
|
||||
'src/node_options-inl.h',
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "node_context_data.h"
|
||||
#include "node_errors.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_native_module.h"
|
||||
#include "node_native_module_env.h"
|
||||
#include "node_platform.h"
|
||||
#include "node_process.h"
|
||||
#include "node_v8_platform-inl.h"
|
||||
@ -351,7 +351,7 @@ Local<Context> NewContext(Isolate* isolate,
|
||||
};
|
||||
Local<Value> arguments[] = {context->Global(), exports};
|
||||
MaybeLocal<Function> maybe_fn =
|
||||
per_process::native_module_loader.LookupAndCompile(
|
||||
native_module::NativeModuleEnv::LookupAndCompile(
|
||||
context, *module, ¶meters, nullptr);
|
||||
if (maybe_fn.IsEmpty()) {
|
||||
return Local<Context>();
|
||||
|
11
src/node.cc
11
src/node.cc
@ -31,7 +31,7 @@
|
||||
#include "node_errors.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_metadata.h"
|
||||
#include "node_native_module.h"
|
||||
#include "node_native_module_env.h"
|
||||
#include "node_options-inl.h"
|
||||
#include "node_perf.h"
|
||||
#include "node_platform.h"
|
||||
@ -118,8 +118,10 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
using native_module::NativeModuleEnv;
|
||||
using options_parser::kAllowedInEnvironment;
|
||||
using options_parser::kDisallowedInEnvironment;
|
||||
|
||||
using v8::Array;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
@ -207,8 +209,7 @@ MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
|
||||
std::vector<Local<Value>>* arguments) {
|
||||
EscapableHandleScope scope(env->isolate());
|
||||
MaybeLocal<Function> maybe_fn =
|
||||
per_process::native_module_loader.LookupAndCompile(
|
||||
env->context(), id, parameters, env);
|
||||
NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env);
|
||||
|
||||
if (maybe_fn.IsEmpty()) {
|
||||
return MaybeLocal<Value>();
|
||||
@ -401,7 +402,7 @@ MaybeLocal<Value> StartMainThreadExecution(Environment* env) {
|
||||
// To allow people to extend Node in different ways, this hook allows
|
||||
// one to drop a file lib/_third_party_main.js into the build
|
||||
// directory which will be executed instead of Node's normal loading.
|
||||
if (per_process::native_module_loader.Exists("_third_party_main")) {
|
||||
if (NativeModuleEnv::Exists("_third_party_main")) {
|
||||
return StartExecution(env, "internal/main/run_third_party_main");
|
||||
}
|
||||
|
||||
@ -724,6 +725,8 @@ int InitializeNodeWithArgs(std::vector<std::string>* argv,
|
||||
per_process::metadata.versions.InitializeIntlVersions();
|
||||
#endif
|
||||
|
||||
NativeModuleEnv::InitializeCodeCache();
|
||||
|
||||
// We should set node_is_initialized here instead of in node::Start,
|
||||
// otherwise embedders using node::Init to initialize everything will not be
|
||||
// able to set it and native modules will not load for them.
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include "node_binding.h"
|
||||
#include "env-inl.h"
|
||||
#include "node_native_module.h"
|
||||
#include "util.h"
|
||||
#include <atomic>
|
||||
#include "env-inl.h"
|
||||
#include "node_native_module_env.h"
|
||||
#include "util.h"
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
#define NODE_BUILTIN_OPENSSL_MODULES(V) V(crypto) V(tls_wrap)
|
||||
@ -593,13 +593,13 @@ void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {
|
||||
exports->SetPrototype(env->context(), Null(env->isolate())).FromJust());
|
||||
DefineConstants(env->isolate(), exports);
|
||||
} else if (!strcmp(*module_v, "natives")) {
|
||||
exports = per_process::native_module_loader.GetSourceObject(env->context());
|
||||
exports = native_module::NativeModuleEnv::GetSourceObject(env->context());
|
||||
// Legacy feature: process.binding('natives').config contains stringified
|
||||
// config.gypi
|
||||
CHECK(exports
|
||||
->Set(env->context(),
|
||||
env->config_string(),
|
||||
per_process::native_module_loader.GetConfigString(
|
||||
native_module::NativeModuleEnv::GetConfigString(
|
||||
env->isolate()))
|
||||
.FromJust());
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
#include "node_native_module.h"
|
||||
#include "node_native_module_env.h"
|
||||
|
||||
// This is supposed to be generated by tools/generate_code_cache.js
|
||||
// The stub here is used when configure is run without `--code-cache-path`
|
||||
@ -8,8 +8,8 @@ namespace node {
|
||||
namespace native_module {
|
||||
|
||||
// The generated source code would insert <std::string, UnionString> pairs
|
||||
// into native_module_loader.code_cache_.
|
||||
void NativeModuleLoader::LoadCodeCache() {}
|
||||
// into NativeModuleLoader::instance.code_cache_.
|
||||
void NativeModuleEnv::InitializeCodeCache() {}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
||||
|
@ -1,40 +1,60 @@
|
||||
#include "node_native_module.h"
|
||||
#include "node_errors.h"
|
||||
#include "util-inl.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
namespace per_process {
|
||||
native_module::NativeModuleLoader native_module_loader;
|
||||
} // namespace per_process
|
||||
|
||||
namespace native_module {
|
||||
|
||||
using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::Context;
|
||||
using v8::DEFAULT;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::HandleScope;
|
||||
using v8::Integer;
|
||||
using v8::IntegrityLevel;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Name;
|
||||
using v8::None;
|
||||
using v8::Object;
|
||||
using v8::PropertyCallbackInfo;
|
||||
using v8::Script;
|
||||
using v8::ScriptCompiler;
|
||||
using v8::ScriptOrigin;
|
||||
using v8::Set;
|
||||
using v8::SideEffectType;
|
||||
using v8::String;
|
||||
using v8::Uint8Array;
|
||||
using v8::Value;
|
||||
|
||||
NativeModuleLoader NativeModuleLoader::instance_;
|
||||
|
||||
NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) {
|
||||
LoadJavaScriptSource();
|
||||
}
|
||||
|
||||
NativeModuleLoader* NativeModuleLoader::GetInstance() {
|
||||
return &instance_;
|
||||
}
|
||||
|
||||
bool NativeModuleLoader::Exists(const char* id) {
|
||||
return source_.find(id) != source_.end();
|
||||
}
|
||||
|
||||
Local<Object> NativeModuleLoader::GetSourceObject(Local<Context> context) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Local<Object> out = Object::New(isolate);
|
||||
for (auto const& x : source_) {
|
||||
Local<String> key = OneByteString(isolate, x.first.c_str(), x.first.size());
|
||||
out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Local<String> NativeModuleLoader::GetConfigString(Isolate* isolate) {
|
||||
return config_.ToStringChecked(isolate);
|
||||
}
|
||||
|
||||
std::vector<std::string> NativeModuleLoader::GetModuleIds() {
|
||||
std::vector<std::string> ids;
|
||||
ids.reserve(source_.size());
|
||||
for (auto const& x : source_) {
|
||||
ids.emplace_back(x.first);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void NativeModuleLoader::InitializeModuleCategories() {
|
||||
if (module_categories_.is_initialized) {
|
||||
@ -105,182 +125,52 @@ void NativeModuleLoader::InitializeModuleCategories() {
|
||||
module_categories_.is_initialized = true;
|
||||
}
|
||||
|
||||
// TODO(joyeecheung): make these more general and put them into util.h
|
||||
Local<Object> MapToObject(Local<Context> context,
|
||||
const NativeModuleRecordMap& in) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Local<Object> out = Object::New(isolate);
|
||||
for (auto const& x : in) {
|
||||
Local<String> key = OneByteString(isolate, x.first.c_str(), x.first.size());
|
||||
out->Set(context, key, x.second.ToStringChecked(isolate)).Check();
|
||||
}
|
||||
return out;
|
||||
const std::set<std::string>& NativeModuleLoader::GetCannotBeRequired() {
|
||||
InitializeModuleCategories();
|
||||
return module_categories_.cannot_be_required;
|
||||
}
|
||||
|
||||
Local<Set> ToJsSet(Local<Context> context,
|
||||
const std::set<std::string>& in) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Local<Set> out = Set::New(isolate);
|
||||
for (auto const& x : in) {
|
||||
out->Add(context, OneByteString(isolate, x.c_str(), x.size()))
|
||||
.ToLocalChecked();
|
||||
}
|
||||
return out;
|
||||
const std::set<std::string>& NativeModuleLoader::GetCanBeRequired() {
|
||||
InitializeModuleCategories();
|
||||
return module_categories_.can_be_required;
|
||||
}
|
||||
|
||||
bool NativeModuleLoader::Exists(const char* id) {
|
||||
return source_.find(id) != source_.end();
|
||||
bool NativeModuleLoader::CanBeRequired(const char* id) {
|
||||
return GetCanBeRequired().count(id) == 1;
|
||||
}
|
||||
|
||||
void NativeModuleLoader::GetModuleCategories(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
per_process::native_module_loader.InitializeModuleCategories();
|
||||
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> result = Object::New(isolate);
|
||||
|
||||
// Copy from the per-process categories
|
||||
std::set<std::string> cannot_be_required =
|
||||
per_process::native_module_loader.module_categories_.cannot_be_required;
|
||||
std::set<std::string> can_be_required =
|
||||
per_process::native_module_loader.module_categories_.can_be_required;
|
||||
|
||||
if (!env->owns_process_state()) {
|
||||
can_be_required.erase("trace_events");
|
||||
cannot_be_required.insert("trace_events");
|
||||
}
|
||||
|
||||
result
|
||||
->Set(context,
|
||||
OneByteString(isolate, "cannotBeRequired"),
|
||||
ToJsSet(context, cannot_be_required))
|
||||
.Check();
|
||||
result
|
||||
->Set(context,
|
||||
OneByteString(isolate, "canBeRequired"),
|
||||
ToJsSet(context, can_be_required))
|
||||
.Check();
|
||||
info.GetReturnValue().Set(result);
|
||||
bool NativeModuleLoader::CannotBeRequired(const char* id) {
|
||||
return GetCannotBeRequired().count(id) == 1;
|
||||
}
|
||||
|
||||
void NativeModuleLoader::GetCacheUsage(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> result = Object::New(isolate);
|
||||
result
|
||||
->Set(env->context(),
|
||||
OneByteString(isolate, "compiledWithCache"),
|
||||
ToJsSet(context, env->native_modules_with_cache))
|
||||
.Check();
|
||||
result
|
||||
->Set(env->context(),
|
||||
OneByteString(isolate, "compiledWithoutCache"),
|
||||
ToJsSet(context, env->native_modules_without_cache))
|
||||
.Check();
|
||||
args.GetReturnValue().Set(result);
|
||||
NativeModuleCacheMap* NativeModuleLoader::code_cache() {
|
||||
return &code_cache_;
|
||||
}
|
||||
|
||||
void NativeModuleLoader::ModuleIdsGetter(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
Isolate* isolate = info.GetIsolate();
|
||||
|
||||
const NativeModuleRecordMap& source_ =
|
||||
per_process::native_module_loader.source_;
|
||||
std::vector<Local<Value>> ids;
|
||||
ids.reserve(source_.size());
|
||||
|
||||
for (auto const& x : source_) {
|
||||
ids.emplace_back(OneByteString(isolate, x.first.c_str(), x.first.size()));
|
||||
}
|
||||
|
||||
info.GetReturnValue().Set(Array::New(isolate, ids.data(), ids.size()));
|
||||
}
|
||||
|
||||
void NativeModuleLoader::ConfigStringGetter(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
info.GetReturnValue().Set(
|
||||
per_process::native_module_loader.GetConfigString(info.GetIsolate()));
|
||||
}
|
||||
|
||||
Local<Object> NativeModuleLoader::GetSourceObject(
|
||||
Local<Context> context) const {
|
||||
return MapToObject(context, source_);
|
||||
}
|
||||
|
||||
Local<String> NativeModuleLoader::GetConfigString(Isolate* isolate) const {
|
||||
return config_.ToStringChecked(isolate);
|
||||
}
|
||||
|
||||
NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) {
|
||||
LoadJavaScriptSource();
|
||||
LoadCodeCache();
|
||||
}
|
||||
|
||||
// This is supposed to be run only by the main thread in
|
||||
// tools/generate_code_cache.js
|
||||
void NativeModuleLoader::GetCodeCache(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
CHECK(env->is_main_thread());
|
||||
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value id_v(isolate, args[0].As<String>());
|
||||
const char* id = *id_v;
|
||||
|
||||
const NativeModuleLoader& loader = per_process::native_module_loader;
|
||||
MaybeLocal<Uint8Array> ret = loader.GetCodeCache(isolate, id);
|
||||
if (!ret.IsEmpty()) {
|
||||
args.GetReturnValue().Set(ret.ToLocalChecked());
|
||||
}
|
||||
}
|
||||
|
||||
// This is supposed to be run only by the main thread in
|
||||
// tools/generate_code_cache.js
|
||||
MaybeLocal<Uint8Array> NativeModuleLoader::GetCodeCache(Isolate* isolate,
|
||||
const char* id) const {
|
||||
EscapableHandleScope scope(isolate);
|
||||
ScriptCompiler::CachedData* NativeModuleLoader::GetCodeCache(
|
||||
const char* id) const {
|
||||
Mutex::ScopedLock lock(code_cache_mutex_);
|
||||
|
||||
ScriptCompiler::CachedData* cached_data = nullptr;
|
||||
const auto it = code_cache_.find(id);
|
||||
if (it == code_cache_.end()) {
|
||||
// The module has not been compiled before.
|
||||
return MaybeLocal<Uint8Array>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cached_data = it->second.get();
|
||||
|
||||
Local<ArrayBuffer> buf = ArrayBuffer::New(isolate, cached_data->length);
|
||||
memcpy(buf->GetContents().Data(), cached_data->data, cached_data->length);
|
||||
return scope.Escape(Uint8Array::New(buf, 0, cached_data->length));
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void NativeModuleLoader::CompileFunction(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value id(env->isolate(), args[0].As<String>());
|
||||
|
||||
MaybeLocal<Function> result = CompileAsModule(env, *id);
|
||||
if (!result.IsEmpty()) {
|
||||
args.GetReturnValue().Set(result.ToLocalChecked());
|
||||
}
|
||||
}
|
||||
|
||||
MaybeLocal<Function> NativeModuleLoader::CompileAsModule(Environment* env,
|
||||
const char* id) {
|
||||
std::vector<Local<String>> parameters = {env->exports_string(),
|
||||
env->require_string(),
|
||||
env->module_string(),
|
||||
env->process_string(),
|
||||
env->internal_binding_string(),
|
||||
env->primordials_string()};
|
||||
return per_process::native_module_loader.LookupAndCompile(
|
||||
env->context(), id, ¶meters, env);
|
||||
MaybeLocal<Function> NativeModuleLoader::CompileAsModule(
|
||||
Local<Context> context,
|
||||
const char* id,
|
||||
NativeModuleLoader::Result* result) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
std::vector<Local<String>> parameters = {
|
||||
FIXED_ONE_BYTE_STRING(isolate, "exports"),
|
||||
FIXED_ONE_BYTE_STRING(isolate, "require"),
|
||||
FIXED_ONE_BYTE_STRING(isolate, "module"),
|
||||
FIXED_ONE_BYTE_STRING(isolate, "process"),
|
||||
FIXED_ONE_BYTE_STRING(isolate, "internalBinding"),
|
||||
FIXED_ONE_BYTE_STRING(isolate, "primordials")};
|
||||
return LookupAndCompile(context, id, ¶meters, result);
|
||||
}
|
||||
|
||||
// Returns Local<Function> of the compiled module if return_code_cache
|
||||
@ -290,7 +180,7 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
Local<Context> context,
|
||||
const char* id,
|
||||
std::vector<Local<String>>* parameters,
|
||||
Environment* optional_env) {
|
||||
NativeModuleLoader::Result* result) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
EscapableHandleScope scope(isolate);
|
||||
|
||||
@ -317,9 +207,9 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
}
|
||||
}
|
||||
|
||||
const bool use_cache = cached_data != nullptr;
|
||||
const bool has_cache = cached_data != nullptr;
|
||||
ScriptCompiler::CompileOptions options =
|
||||
use_cache ? ScriptCompiler::kConsumeCodeCache
|
||||
has_cache ? ScriptCompiler::kConsumeCodeCache
|
||||
: ScriptCompiler::kEagerCompile;
|
||||
ScriptCompiler::Source script_source(source, origin, cached_data);
|
||||
|
||||
@ -346,22 +236,10 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
// it only starts after the Environment is created, so the per_context.js
|
||||
// will never be in any of these two sets, but the two sets are only for
|
||||
// testing anyway.
|
||||
if (use_cache) {
|
||||
if (optional_env != nullptr) {
|
||||
// This could happen when Node is run with any v8 flag, but
|
||||
// the cache is not generated with one
|
||||
if (script_source.GetCachedData()->rejected) {
|
||||
optional_env->native_modules_without_cache.insert(id);
|
||||
} else {
|
||||
optional_env->native_modules_with_cache.insert(id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (optional_env != nullptr) {
|
||||
optional_env->native_modules_without_cache.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
*result = (has_cache && !script_source.GetCachedData()->rejected)
|
||||
? Result::kWithCache
|
||||
: Result::kWithoutCache;
|
||||
// Generate new cache for next compilation
|
||||
std::unique_ptr<ScriptCompiler::CachedData> new_cached_data(
|
||||
ScriptCompiler::CreateCodeCacheForFunction(fun));
|
||||
@ -373,56 +251,5 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
return scope.Escape(fun);
|
||||
}
|
||||
|
||||
void NativeModuleLoader::Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
|
||||
CHECK(target
|
||||
->SetAccessor(env->context(),
|
||||
env->config_string(),
|
||||
ConfigStringGetter,
|
||||
nullptr,
|
||||
MaybeLocal<Value>(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.FromJust());
|
||||
CHECK(target
|
||||
->SetAccessor(env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "moduleIds"),
|
||||
ModuleIdsGetter,
|
||||
nullptr,
|
||||
MaybeLocal<Value>(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.FromJust());
|
||||
|
||||
CHECK(target
|
||||
->SetAccessor(
|
||||
env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "moduleCategories"),
|
||||
GetModuleCategories,
|
||||
nullptr,
|
||||
env->as_callback_data(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.FromJust());
|
||||
|
||||
env->SetMethod(
|
||||
target, "getCacheUsage", NativeModuleLoader::GetCacheUsage);
|
||||
env->SetMethod(
|
||||
target, "compileFunction", NativeModuleLoader::CompileFunction);
|
||||
env->SetMethod(target, "getCodeCache", NativeModuleLoader::GetCodeCache);
|
||||
// internalBinding('native_module') should be frozen
|
||||
target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).Check();
|
||||
}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
||||
|
||||
NODE_MODULE_CONTEXT_AWARE_INTERNAL(
|
||||
native_module, node::native_module::NativeModuleLoader::Initialize)
|
||||
|
@ -4,9 +4,9 @@
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "env.h"
|
||||
#include "node_mutex.h"
|
||||
#include "node_union_bytes.h"
|
||||
#include "v8.h"
|
||||
@ -23,79 +23,57 @@ using NativeModuleCacheMap =
|
||||
// handles compilation and caching of builtin modules (NativeModule)
|
||||
// and bootstrappers, whose source are bundled into the binary
|
||||
// as static data.
|
||||
// This class should not depend on a particular isolate, context, or
|
||||
// environment. Rather it should take them as arguments when necessary.
|
||||
// The instances of this class are per-process.
|
||||
// This class should not depend on any Environment, or depend on access to
|
||||
// the its own singleton - that should be encapsulated in NativeModuleEnv
|
||||
// instead.
|
||||
class NativeModuleLoader {
|
||||
public:
|
||||
private:
|
||||
// Only allow access from friends.
|
||||
friend class NativeModuleEnv;
|
||||
friend class CodeCacheBuilder;
|
||||
|
||||
NativeModuleLoader();
|
||||
// TODO(joyeecheung): maybe we should make this a singleton, instead of
|
||||
// putting it in per_process.
|
||||
NativeModuleLoader(const NativeModuleLoader&) = delete;
|
||||
NativeModuleLoader& operator=(const NativeModuleLoader&) = delete;
|
||||
static NativeModuleLoader* GetInstance();
|
||||
|
||||
static void Initialize(v8::Local<v8::Object> target,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv);
|
||||
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context) const;
|
||||
// Returns config.gypi as a JSON string
|
||||
v8::Local<v8::String> GetConfigString(v8::Isolate* isolate) const;
|
||||
// Generated by tools/js2c.py as node_javascript.cc
|
||||
void LoadJavaScriptSource(); // Loads data into source_
|
||||
UnionBytes GetConfig(); // Return data for config.gypi
|
||||
|
||||
bool Exists(const char* id);
|
||||
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context);
|
||||
v8::Local<v8::String> GetConfigString(v8::Isolate* isolate);
|
||||
std::vector<std::string> GetModuleIds();
|
||||
|
||||
// For bootstrappers optional_env may be a nullptr.
|
||||
struct ModuleCategories {
|
||||
bool is_initialized = false;
|
||||
std::set<std::string> can_be_required;
|
||||
std::set<std::string> cannot_be_required;
|
||||
};
|
||||
void InitializeModuleCategories();
|
||||
const std::set<std::string>& GetCannotBeRequired();
|
||||
const std::set<std::string>& GetCanBeRequired();
|
||||
|
||||
bool CanBeRequired(const char* id);
|
||||
bool CannotBeRequired(const char* id);
|
||||
|
||||
NativeModuleCacheMap* code_cache();
|
||||
v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const;
|
||||
enum class Result { kWithCache, kWithoutCache };
|
||||
// If an exception is encountered (e.g. source code contains
|
||||
// syntax error), the returned value is empty.
|
||||
v8::MaybeLocal<v8::Function> LookupAndCompile(
|
||||
v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
std::vector<v8::Local<v8::String>>* parameters,
|
||||
Environment* optional_env);
|
||||
|
||||
private:
|
||||
static void GetModuleCategories(
|
||||
v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
// Passing ids of builtin module source code into JS land as
|
||||
// internalBinding('native_module').moduleIds
|
||||
static void ModuleIdsGetter(v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
// Passing config.gypi into JS land as internalBinding('native_module').config
|
||||
static void ConfigStringGetter(
|
||||
v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
// Get code cache for a specific native module
|
||||
static void GetCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
v8::MaybeLocal<v8::Uint8Array> GetCodeCache(v8::Isolate* isolate,
|
||||
const char* id) const;
|
||||
// Compile a specific native module as a function
|
||||
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
// Generated by tools/js2c.py as node_javascript.cc
|
||||
void LoadJavaScriptSource(); // Loads data into source_
|
||||
UnionBytes GetConfig(); // Return data for config.gypi
|
||||
|
||||
// Generated by tools/generate_code_cache.js as node_code_cache.cc when
|
||||
// the build is configured with --code-cache-path=.... They are noops
|
||||
// in node_code_cache_stub.cc
|
||||
void LoadCodeCache(); // Loads data into code_cache_
|
||||
|
||||
// Compile a script as a NativeModule that can be loaded via
|
||||
// NativeModule.p.require in JS land.
|
||||
static v8::MaybeLocal<v8::Function> CompileAsModule(Environment* env,
|
||||
const char* id);
|
||||
|
||||
void InitializeModuleCategories();
|
||||
struct ModuleCategories {
|
||||
bool is_initialized = false;
|
||||
std::set<std::string> can_be_required;
|
||||
std::set<std::string> cannot_be_required;
|
||||
};
|
||||
Result* result);
|
||||
v8::MaybeLocal<v8::Function> CompileAsModule(v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
Result* result);
|
||||
|
||||
static NativeModuleLoader instance_;
|
||||
ModuleCategories module_categories_;
|
||||
|
||||
NativeModuleRecordMap source_;
|
||||
NativeModuleCacheMap code_cache_;
|
||||
UnionBytes config_;
|
||||
@ -103,13 +81,8 @@ class NativeModuleLoader {
|
||||
// Used to synchronize access to the code cache map
|
||||
Mutex code_cache_mutex_;
|
||||
};
|
||||
|
||||
} // namespace native_module
|
||||
|
||||
namespace per_process {
|
||||
extern native_module::NativeModuleLoader native_module_loader;
|
||||
} // namespace per_process
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
229
src/node_native_module_env.cc
Normal file
229
src/node_native_module_env.cc
Normal file
@ -0,0 +1,229 @@
|
||||
#include "node_native_module_env.h"
|
||||
#include "env-inl.h"
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
|
||||
using v8::ArrayBuffer;
|
||||
using v8::Context;
|
||||
using v8::DEFAULT;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::IntegrityLevel;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Name;
|
||||
using v8::None;
|
||||
using v8::Object;
|
||||
using v8::PropertyCallbackInfo;
|
||||
using v8::ScriptCompiler;
|
||||
using v8::Set;
|
||||
using v8::SideEffectType;
|
||||
using v8::String;
|
||||
using v8::Uint8Array;
|
||||
using v8::Value;
|
||||
|
||||
// TODO(joyeecheung): make these more general and put them into util.h
|
||||
Local<Set> ToJsSet(Local<Context> context, const std::set<std::string>& in) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Local<Set> out = Set::New(isolate);
|
||||
for (auto const& x : in) {
|
||||
out->Add(context, OneByteString(isolate, x.c_str(), x.size()))
|
||||
.ToLocalChecked();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool NativeModuleEnv::Exists(const char* id) {
|
||||
return NativeModuleLoader::GetInstance()->Exists(id);
|
||||
}
|
||||
|
||||
Local<Object> NativeModuleEnv::GetSourceObject(Local<Context> context) {
|
||||
return NativeModuleLoader::GetInstance()->GetSourceObject(context);
|
||||
}
|
||||
|
||||
Local<String> NativeModuleEnv::GetConfigString(Isolate* isolate) {
|
||||
return NativeModuleLoader::GetInstance()->GetConfigString(isolate);
|
||||
}
|
||||
|
||||
void NativeModuleEnv::GetModuleCategories(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> result = Object::New(isolate);
|
||||
|
||||
// Copy from the per-process categories
|
||||
std::set<std::string> cannot_be_required =
|
||||
NativeModuleLoader::GetInstance()->GetCannotBeRequired();
|
||||
std::set<std::string> can_be_required =
|
||||
NativeModuleLoader::GetInstance()->GetCanBeRequired();
|
||||
|
||||
if (!env->owns_process_state()) {
|
||||
can_be_required.erase("trace_events");
|
||||
cannot_be_required.insert("trace_events");
|
||||
}
|
||||
|
||||
result
|
||||
->Set(context,
|
||||
OneByteString(isolate, "cannotBeRequired"),
|
||||
ToJsSet(context, cannot_be_required))
|
||||
.FromJust();
|
||||
result
|
||||
->Set(context,
|
||||
OneByteString(isolate, "canBeRequired"),
|
||||
ToJsSet(context, can_be_required))
|
||||
.FromJust();
|
||||
info.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void NativeModuleEnv::GetCacheUsage(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> result = Object::New(isolate);
|
||||
result
|
||||
->Set(env->context(),
|
||||
OneByteString(isolate, "compiledWithCache"),
|
||||
ToJsSet(context, env->native_modules_with_cache))
|
||||
.FromJust();
|
||||
result
|
||||
->Set(env->context(),
|
||||
OneByteString(isolate, "compiledWithoutCache"),
|
||||
ToJsSet(context, env->native_modules_without_cache))
|
||||
.FromJust();
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void NativeModuleEnv::ModuleIdsGetter(Local<Name> property,
|
||||
const PropertyCallbackInfo<Value>& info) {
|
||||
Isolate* isolate = info.GetIsolate();
|
||||
|
||||
std::vector<std::string> ids =
|
||||
NativeModuleLoader::GetInstance()->GetModuleIds();
|
||||
info.GetReturnValue().Set(
|
||||
ToV8Value(isolate->GetCurrentContext(), ids).ToLocalChecked());
|
||||
}
|
||||
|
||||
void NativeModuleEnv::ConfigStringGetter(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
info.GetReturnValue().Set(GetConfigString(info.GetIsolate()));
|
||||
}
|
||||
|
||||
void NativeModuleEnv::RecordResult(const char* id,
|
||||
NativeModuleLoader::Result result,
|
||||
Environment* env) {
|
||||
if (result == NativeModuleLoader::Result::kWithCache) {
|
||||
env->native_modules_with_cache.insert(id);
|
||||
} else {
|
||||
env->native_modules_without_cache.insert(id);
|
||||
}
|
||||
}
|
||||
void NativeModuleEnv::CompileFunction(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value id_v(env->isolate(), args[0].As<String>());
|
||||
const char* id = *id_v;
|
||||
NativeModuleLoader::Result result;
|
||||
MaybeLocal<Function> maybe =
|
||||
NativeModuleLoader::GetInstance()->CompileAsModule(
|
||||
env->context(), id, &result);
|
||||
RecordResult(id, result, env);
|
||||
if (!maybe.IsEmpty()) {
|
||||
args.GetReturnValue().Set(maybe.ToLocalChecked());
|
||||
}
|
||||
}
|
||||
|
||||
// Returns Local<Function> of the compiled module if return_code_cache
|
||||
// is false (we are only compiling the function).
|
||||
// Otherwise return a Local<Object> containing the cache.
|
||||
MaybeLocal<Function> NativeModuleEnv::LookupAndCompile(
|
||||
Local<Context> context,
|
||||
const char* id,
|
||||
std::vector<Local<String>>* parameters,
|
||||
Environment* optional_env) {
|
||||
NativeModuleLoader::Result result;
|
||||
MaybeLocal<Function> maybe =
|
||||
NativeModuleLoader::GetInstance()->LookupAndCompile(
|
||||
context, id, parameters, &result);
|
||||
if (optional_env != nullptr) {
|
||||
RecordResult(id, result, optional_env);
|
||||
}
|
||||
return maybe;
|
||||
}
|
||||
|
||||
// This is supposed to be run only by the main thread in
|
||||
// tools/generate_code_cache.js
|
||||
void NativeModuleEnv::GetCodeCache(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
CHECK(env->is_main_thread());
|
||||
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value id_v(isolate, args[0].As<String>());
|
||||
const char* id = *id_v;
|
||||
|
||||
ScriptCompiler::CachedData* cached_data =
|
||||
NativeModuleLoader::GetInstance()->GetCodeCache(id);
|
||||
if (cached_data != nullptr) {
|
||||
Local<ArrayBuffer> buf = ArrayBuffer::New(isolate, cached_data->length);
|
||||
memcpy(buf->GetContents().Data(), cached_data->data, cached_data->length);
|
||||
args.GetReturnValue().Set(Uint8Array::New(buf, 0, cached_data->length));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(joyeecheung): It is somewhat confusing that Class::Initialize
|
||||
// is used to initilaize to the binding, but it is the current convention.
|
||||
// Rename this across the code base to something that makes more sense.
|
||||
void NativeModuleEnv::Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
|
||||
target
|
||||
->SetAccessor(env->context(),
|
||||
env->config_string(),
|
||||
ConfigStringGetter,
|
||||
nullptr,
|
||||
MaybeLocal<Value>(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.Check();
|
||||
target
|
||||
->SetAccessor(env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "moduleIds"),
|
||||
ModuleIdsGetter,
|
||||
nullptr,
|
||||
MaybeLocal<Value>(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.Check();
|
||||
|
||||
target
|
||||
->SetAccessor(env->context(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "moduleCategories"),
|
||||
GetModuleCategories,
|
||||
nullptr,
|
||||
env->as_callback_data(),
|
||||
DEFAULT,
|
||||
None,
|
||||
SideEffectType::kHasNoSideEffect)
|
||||
.Check();
|
||||
|
||||
env->SetMethod(target, "getCacheUsage", NativeModuleEnv::GetCacheUsage);
|
||||
env->SetMethod(target, "getCodeCache", NativeModuleEnv::GetCodeCache);
|
||||
env->SetMethod(target, "compileFunction", NativeModuleEnv::CompileFunction);
|
||||
// internalBinding('native_module') should be frozen
|
||||
target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust();
|
||||
}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
||||
|
||||
NODE_MODULE_CONTEXT_AWARE_INTERNAL(
|
||||
native_module, node::native_module::NativeModuleEnv::Initialize)
|
64
src/node_native_module_env.h
Normal file
64
src/node_native_module_env.h
Normal file
@ -0,0 +1,64 @@
|
||||
#ifndef SRC_NODE_NATIVE_MODULE_ENV_H_
|
||||
#define SRC_NODE_NATIVE_MODULE_ENV_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include "node_native_module.h"
|
||||
|
||||
namespace node {
|
||||
class Environment;
|
||||
|
||||
namespace native_module {
|
||||
|
||||
class NativeModuleEnv {
|
||||
public:
|
||||
static void Initialize(v8::Local<v8::Object> target,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv);
|
||||
|
||||
static v8::MaybeLocal<v8::Function> LookupAndCompile(
|
||||
v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
std::vector<v8::Local<v8::String>>* parameters,
|
||||
Environment* optional_env);
|
||||
|
||||
static v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context);
|
||||
// Returns config.gypi as a JSON string
|
||||
static v8::Local<v8::String> GetConfigString(v8::Isolate* isolate);
|
||||
static bool Exists(const char* id);
|
||||
|
||||
// Loads data into NativeModuleLoader::.instance.code_cache_
|
||||
// Generated by mkcodecache as node_code_cache.cc when
|
||||
// the build is configured with --code-cache-path=.... They are noops
|
||||
// in node_code_cache_stub.cc
|
||||
static void InitializeCodeCache();
|
||||
|
||||
private:
|
||||
static void RecordResult(const char* id,
|
||||
NativeModuleLoader::Result result,
|
||||
Environment* env);
|
||||
static void GetModuleCategories(
|
||||
v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
// Passing ids of builtin module source code into JS land as
|
||||
// internalBinding('native_module').moduleIds
|
||||
static void ModuleIdsGetter(v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
// Passing config.gypi into JS land as internalBinding('native_module').config
|
||||
static void ConfigStringGetter(
|
||||
v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
// Compile a specific native module as a function
|
||||
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
};
|
||||
|
||||
} // namespace native_module
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#endif // SRC_NODE_NATIVE_MODULE_ENV_H_
|
@ -7,7 +7,6 @@
|
||||
// A union of const uint8_t* or const uint16_t* data that can be
|
||||
// turned into external v8::String when given an isolate.
|
||||
|
||||
#include "env.h"
|
||||
#include "v8.h"
|
||||
|
||||
namespace node {
|
||||
|
@ -30,9 +30,9 @@ if (child.status !== 0) {
|
||||
}
|
||||
|
||||
// Verifies that:
|
||||
// - node::LoadCodeCache()
|
||||
// - NativeModuleEnv::InitializeCodeCache()
|
||||
// are defined in the generated code.
|
||||
// See src/node_native_module.h for explanations.
|
||||
// See src/node_native_module_env.h for explanations.
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream(dest),
|
||||
@ -42,7 +42,7 @@ const rl = readline.createInterface({
|
||||
let hasCacheDef = false;
|
||||
|
||||
rl.on('line', common.mustCallAtLeast((line) => {
|
||||
if (line.includes('LoadCodeCache(')) {
|
||||
if (line.includes('InitializeCodeCache(')) {
|
||||
hasCacheDef = true;
|
||||
}
|
||||
}, 2));
|
||||
|
@ -63,7 +63,7 @@ function getInitalizer(key, cache) {
|
||||
`${defName}, static_cast<int>(arraysize(${defName})), ` +
|
||||
'policy)';
|
||||
const initializer =
|
||||
'code_cache_.emplace(\n' +
|
||||
'code_cache->emplace(\n' +
|
||||
` "${key}",\n` +
|
||||
` ${dataDef}\n` +
|
||||
');';
|
||||
@ -107,8 +107,7 @@ for (const key of [...canBeRequired].sort(lexical)) {
|
||||
`, total = ${formatSize(totalCacheSize)}`);
|
||||
}
|
||||
|
||||
const result = `#include "node_native_module.h"
|
||||
#include "node_internals.h"
|
||||
const result = `#include "node_native_module_env.h"
|
||||
|
||||
// This file is generated by tools/generate_code_cache.js
|
||||
// and is used when configure is run with \`--code-cache-path\`
|
||||
@ -117,7 +116,13 @@ namespace node {
|
||||
namespace native_module {
|
||||
${cacheDefinitions.join('\n\n')}
|
||||
|
||||
void NativeModuleLoader::LoadCodeCache() {
|
||||
void NativeModuleEnv::InitializeCodeCache() {
|
||||
NativeModuleCacheMap* code_cache =
|
||||
NativeModuleLoader::GetInstance()->code_cache();
|
||||
if (!code_cache->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto policy = v8::ScriptCompiler::CachedData::BufferPolicy::BufferNotOwned;
|
||||
${cacheInitializers.join('\n ')}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user