Skip to content

src: make a Environment-independent proxy class for NativeModuleLoader #27160

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

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -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',
4 changes: 2 additions & 2 deletions src/api/environment.cc
Original file line number Diff line number Diff line change
@@ -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, &parameters, nullptr);
if (maybe_fn.IsEmpty()) {
return Local<Context>();
11 changes: 7 additions & 4 deletions src/node.cc
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include "node_binding.h"
#include <atomic>
#include "env-inl.h"
#include "node_native_module.h"
#include "node_native_module_env.h"
#include "util.h"
#include <atomic>

#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 {
6 changes: 3 additions & 3 deletions src/node_code_cache_stub.cc
Original file line number Diff line number Diff line change
@@ -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
319 changes: 73 additions & 246 deletions src/node_native_module.cc
Original file line number Diff line number Diff line change
@@ -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);
}

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);
}

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();
bool NativeModuleLoader::CannotBeRequired(const char* id) {
return GetCannotBeRequired().count(id) == 1;
}

// 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());
}
NativeModuleCacheMap* NativeModuleLoader::code_cache() {
return &code_cache_;
}

// 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>();
}

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));
}

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());
return nullptr;
}
return it->second.get();
}

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, &parameters, 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, &parameters, 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)
97 changes: 35 additions & 62 deletions src/node_native_module.h
Original file line number Diff line number Diff line change
@@ -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,93 +23,66 @@ 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 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;

bool Exists(const char* id);

// For bootstrappers optional_env may be a nullptr.
// 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);
static NativeModuleLoader* GetInstance();

// 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);
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();

void InitializeModuleCategories();
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();

ModuleCategories module_categories_;
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,
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_;

// 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 changes: 229 additions & 0 deletions src/node_native_module_env.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 64 additions & 0 deletions src/node_native_module_env.h
Original file line number Diff line number Diff line change
@@ -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_
1 change: 0 additions & 1 deletion src/node_union_bytes.h
Original file line number Diff line number Diff line change
@@ -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 {
6 changes: 3 additions & 3 deletions test/code-cache/test-code-cache-generator.js
Original file line number Diff line number Diff line change
@@ -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));
13 changes: 9 additions & 4 deletions tools/generate_code_cache.js
Original file line number Diff line number Diff line change
@@ -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 ')}
}