Skip to content

Commit

Permalink
src: make a Environment-independent proxy class for NativeModuleLoader
Browse files Browse the repository at this point in the history
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: #27160
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Richard Lau <[email protected]>
joyeecheung committed Apr 13, 2019

Unverified

This user has not yet uploaded their public signing key.
1 parent 9b6b567 commit dfd7e99
Showing 12 changed files with 431 additions and 329 deletions.
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 ')}
}

0 comments on commit dfd7e99

Please sign in to comment.