#include "node_native_module.h" #include "node_errors.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::ArrayBufferCreationMode; 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; // TODO(joyeecheung): make these more general and put them into util.h Local MapToObject(Local context, const NativeModuleRecordMap& in) { Isolate* isolate = context->GetIsolate(); Local out = Object::New(isolate); for (auto const& x : in) { Local key = OneByteString(isolate, x.first.c_str(), x.first.size()); out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust(); } return out; } Local ToJsSet(Local context, const std::set& in) { Isolate* isolate = context->GetIsolate(); Local out = Set::New(isolate); for (auto const& x : in) { out->Add(context, OneByteString(isolate, x.c_str(), x.size())) .ToLocalChecked(); } return out; } bool NativeModuleLoader::Exists(const char* id) { return source_.find(id) != source_.end(); } void NativeModuleLoader::GetCacheUsage( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Local context = env->context(); Local 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 NativeModuleLoader::ModuleIdsGetter( Local property, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); const NativeModuleRecordMap& source_ = per_process::native_module_loader.source_; std::vector> ids; ids.reserve(source_.size()); for (auto const& x : source_) { ids.push_back(OneByteString(isolate, x.first.c_str(), x.first.size())); } info.GetReturnValue().Set(Array::New(isolate, ids.data(), ids.size())); } void NativeModuleLoader::ConfigStringGetter( Local property, const PropertyCallbackInfo& info) { info.GetReturnValue().Set( per_process::native_module_loader.GetConfigString(info.GetIsolate())); } Local NativeModuleLoader::GetSourceObject( Local context) const { return MapToObject(context, source_); } Local 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& 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()); const char* id = *id_v; const NativeModuleLoader& loader = per_process::native_module_loader; MaybeLocal 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 NativeModuleLoader::GetCodeCache(Isolate* isolate, const char* id) const { EscapableHandleScope scope(isolate); 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(); } cached_data = it->second.get(); MallocedBuffer copied(cached_data->length); memcpy(copied.data, cached_data->data, cached_data->length); Local buf = ArrayBuffer::New(isolate, copied.release(), cached_data->length, ArrayBufferCreationMode::kInternalized); return scope.Escape(Uint8Array::New(buf, 0, cached_data->length)); } void NativeModuleLoader::CompileFunction( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsString()); node::Utf8Value id(env->isolate(), args[0].As()); MaybeLocal result = CompileAsModule(env, *id); if (!result.IsEmpty()) { args.GetReturnValue().Set(result.ToLocalChecked()); } } MaybeLocal NativeModuleLoader::CompileAsModule(Environment* env, const char* id) { std::vector> 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); } // Returns Local of the compiled module if return_code_cache // is false (we are only compiling the function). // Otherwise return a Local containing the cache. MaybeLocal NativeModuleLoader::LookupAndCompile( Local context, const char* id, std::vector>* parameters, Environment* optional_env) { Isolate* isolate = context->GetIsolate(); EscapableHandleScope scope(isolate); Local ret; // Used to convert to MaybeLocal before return const auto source_it = source_.find(id); CHECK_NE(source_it, source_.end()); Local source = source_it->second.ToStringChecked(isolate); std::string filename_s = id + std::string(".js"); Local filename = OneByteString(isolate, filename_s.c_str(), filename_s.size()); Local line_offset = Integer::New(isolate, 0); Local column_offset = Integer::New(isolate, 0); ScriptOrigin origin(filename, line_offset, column_offset, True(isolate)); Mutex::ScopedLock lock(code_cache_mutex_); ScriptCompiler::CachedData* cached_data = nullptr; { auto cache_it = code_cache_.find(id); if (cache_it != code_cache_.end()) { // Transfer ownership to ScriptCompiler::Source later. cached_data = cache_it->second.release(); code_cache_.erase(cache_it); } } const bool use_cache = cached_data != nullptr; ScriptCompiler::CompileOptions options = use_cache ? ScriptCompiler::kConsumeCodeCache : ScriptCompiler::kEagerCompile; ScriptCompiler::Source script_source(source, origin, cached_data); MaybeLocal maybe_fun = ScriptCompiler::CompileFunctionInContext(context, &script_source, parameters->size(), parameters->data(), 0, nullptr, options); // This could fail when there are early errors in the native modules, // e.g. the syntax errors if (maybe_fun.IsEmpty()) { // In the case of early errors, v8 is already capable of // decorating the stack for us - note that we use CompileFunctionInContext // so there is no need to worry about wrappers. return MaybeLocal(); } Local fun = maybe_fun.ToLocalChecked(); // XXX(joyeecheung): this bookkeeping is not exactly accurate because // 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); } } // Generate new cache for next compilation std::unique_ptr new_cached_data( ScriptCompiler::CreateCodeCacheForFunction(fun)); CHECK_NOT_NULL(new_cached_data); // The old entry should've been erased by now so we can just emplace code_cache_.emplace(id, std::move(new_cached_data)); return scope.Escape(fun); } void NativeModuleLoader::Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); CHECK(target ->SetAccessor(env->context(), env->config_string(), ConfigStringGetter, nullptr, MaybeLocal(), DEFAULT, None, SideEffectType::kHasNoSideEffect) .FromJust()); CHECK(target ->SetAccessor(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "moduleIds"), ModuleIdsGetter, nullptr, MaybeLocal(), 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).FromJust(); } } // namespace native_module } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL( native_module, node::native_module::NativeModuleLoader::Initialize)