diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 5e653c81c6e30d..d79e24c4188d4a 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -114,7 +114,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE // but this gets called again from `defaultLoad`/`defaultLoadSync`. if (getOptionValue('--experimental-detect-module')) { const format = source ? - (containsModuleSyntax(`${source}`, fileURLToPath(url)) ? 'module' : 'commonjs') : + (containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs') : null; if (format === 'module') { // This module has a .js extension, a package.json with no `type` field, and ESM syntax. @@ -158,7 +158,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE if (!source) { return null; } const format = getFormatOfExtensionlessFile(url); if (format === 'module') { - return containsModuleSyntax(`${source}`, fileURLToPath(url)) ? 'module' : 'commonjs'; + return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs'; } return format; } diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index b39dc5e6638671..f7ad4cd1be6c8d 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -19,15 +19,6 @@ const { } = require('internal/errors').codes; const { BuiltinModule } = require('internal/bootstrap/realm'); -const { - shouldRetryAsESM: contextifyShouldRetryAsESM, - constants: { - syntaxDetectionErrors: { - esmSyntaxErrorMessages, - throwsOnlyInCommonJSErrorMessages, - }, - }, -} = internalBinding('contextify'); const { validateString } = require('internal/validators'); const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched. const internalFS = require('internal/fs/utils'); @@ -329,30 +320,6 @@ function normalizeReferrerURL(referrerName) { } -let esmSyntaxErrorMessagesSet; // Declared lazily in shouldRetryAsESM -let throwsOnlyInCommonJSErrorMessagesSet; // Declared lazily in shouldRetryAsESM -/** - * After an attempt to parse a module as CommonJS throws an error, should we try again as ESM? - * We only want to try again as ESM if the error is due to syntax that is only valid in ESM; and if the CommonJS parse - * throws on an error that would not have been a syntax error in ESM (like via top-level `await` or a lexical - * redeclaration of one of the CommonJS variables) then we need to parse again to see if it would have thrown in ESM. - * @param {string} errorMessage The string message thrown by V8 when attempting to parse as CommonJS - * @param {string} source Module contents - */ -function shouldRetryAsESM(errorMessage, source) { - esmSyntaxErrorMessagesSet ??= new SafeSet(esmSyntaxErrorMessages); - if (esmSyntaxErrorMessagesSet.has(errorMessage)) { - return true; - } - - throwsOnlyInCommonJSErrorMessagesSet ??= new SafeSet(throwsOnlyInCommonJSErrorMessages); - if (throwsOnlyInCommonJSErrorMessagesSet.has(errorMessage)) { - return /** @type {boolean} */(contextifyShouldRetryAsESM(source)); - } - - return false; -} - /** * @param {string|undefined} url URL to convert to filename */ @@ -382,7 +349,6 @@ module.exports = { loadBuiltinModule, makeRequireFunction, normalizeReferrerURL, - shouldRetryAsESM, stripBOM, toRealPath, hasStartedUserCJSExecution() { diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index c06c8cc72cc9f4..fd592f45ffd3e9 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -1,7 +1,9 @@ 'use strict'; const { + ObjectGetPrototypeOf, StringPrototypeEndsWith, + SyntaxErrorPrototype, globalThis, } = primordials; @@ -159,7 +161,7 @@ function runEntryPointWithESMLoader(callback) { function executeUserEntryPoint(main = process.argv[1]) { const resolvedMain = resolveMainPath(main); const useESMLoader = shouldUseESMLoader(resolvedMain); - + let mainURL; // Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first // try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM. let retryAsESM = false; @@ -167,19 +169,21 @@ function executeUserEntryPoint(main = process.argv[1]) { const cjsLoader = require('internal/modules/cjs/loader'); const { Module } = cjsLoader; if (getOptionValue('--experimental-detect-module')) { + // TODO(joyeecheung): handle this in the CJS loader. Don't try-catch here. try { // Module._load is the monkey-patchable CJS module loader. Module._load(main, null, true); } catch (error) { - const source = cjsLoader.entryPointSource; - const { shouldRetryAsESM } = require('internal/modules/helpers'); - retryAsESM = shouldRetryAsESM(error.message, source); - // In case the entry point is a large file, such as a bundle, - // ensure no further references can prevent it being garbage-collected. - cjsLoader.entryPointSource = undefined; + if (error != null && ObjectGetPrototypeOf(error) === SyntaxErrorPrototype) { + const { shouldRetryAsESM } = internalBinding('contextify'); + const mainPath = resolvedMain || main; + mainURL = pathToFileURL(mainPath).href; + retryAsESM = shouldRetryAsESM(error.message, cjsLoader.entryPointSource, mainURL); + // In case the entry point is a large file, such as a bundle, + // ensure no further references can prevent it being garbage-collected. + cjsLoader.entryPointSource = undefined; + } if (!retryAsESM) { - const { enrichCJSError } = require('internal/modules/esm/translators'); - enrichCJSError(error, source, resolvedMain); throw error; } } @@ -190,7 +194,9 @@ function executeUserEntryPoint(main = process.argv[1]) { if (useESMLoader || retryAsESM) { const mainPath = resolvedMain || main; - const mainURL = pathToFileURL(mainPath).href; + if (mainURL === undefined) { + mainURL = pathToFileURL(mainPath).href; + } runEntryPointWithESMLoader((cascadedLoader) => { // Note that if the graph contains unsettled TLA, this may never resolve diff --git a/src/module_wrap.cc b/src/module_wrap.cc index c80ef95b7a78f1..fd3d9c0ceaf317 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -142,9 +142,17 @@ v8::Maybe ModuleWrap::CheckUnsettledTopLevelAwait() { return v8::Just(false); } -// new ModuleWrap(url, context, source, lineOffset, columnOffset, cachedData) +Local ModuleWrap::GetHostDefinedOptions( + Isolate* isolate, Local id_symbol) { + Local host_defined_options = + PrimitiveArray::New(isolate, HostDefinedOptions::kLength); + host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol); + return host_defined_options; +} + +// new ModuleWrap(url, context, source, lineOffset, columnOffset[, cachedData]); // new ModuleWrap(url, context, source, lineOffset, columOffset, -// hostDefinedOption) +// idSymbol); // new ModuleWrap(url, context, exportNames, evaluationCallback[, cjsModule]) void ModuleWrap::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); @@ -183,9 +191,10 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { // cjsModule]) CHECK(args[3]->IsFunction()); } else { - // new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData) + // new ModuleWrap(url, context, source, lineOffset, columOffset[, + // cachedData]); // new ModuleWrap(url, context, source, lineOffset, columOffset, - // hostDefinedOption) + // idSymbol); CHECK(args[2]->IsString()); CHECK(args[3]->IsNumber()); line_offset = args[3].As()->Value(); @@ -199,7 +208,7 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { } else { id_symbol = Symbol::New(isolate, url); } - host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol); + host_defined_options = GetHostDefinedOptions(isolate, id_symbol); if (that->SetPrivate(context, realm->isolate_data()->host_defined_option_symbol(), @@ -234,50 +243,34 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { module = Module::CreateSyntheticModule( isolate, url, span, SyntheticModuleEvaluationStepsCallback); } else { - ScriptCompiler::CachedData* cached_data = nullptr; - bool used_cache_from_user = false; + // When we are compiling for the default loader, this will be + // std::nullopt, and CompileSourceTextModule() should use + // on-disk cache. + std::optional user_cached_data; + if (id_symbol != + realm->isolate_data()->source_text_module_default_hdo()) { + user_cached_data = nullptr; + } if (args[5]->IsArrayBufferView()) { - DCHECK(!can_use_builtin_cache); // We don't use this option internally. - used_cache_from_user = true; + CHECK(!can_use_builtin_cache); // We don't use this option internally. Local cached_data_buf = args[5].As(); uint8_t* data = static_cast(cached_data_buf->Buffer()->Data()); - cached_data = + user_cached_data = new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); } - Local source_text = args[2].As(); - ScriptOrigin origin(isolate, - url, - line_offset, - column_offset, - true, // is cross origin - -1, // script id - Local(), // source map URL - false, // is opaque (?) - false, // is WASM - true, // is ES Module - host_defined_options); - - CompileCacheEntry* cache_entry = nullptr; - if (can_use_builtin_cache && realm->env()->use_compile_cache()) { - cache_entry = realm->env()->compile_cache_handler()->GetOrInsert( - source_text, url, CachedCodeType::kESM); - } - if (cache_entry != nullptr && cache_entry->cache != nullptr) { - // source will take ownership of cached_data. - cached_data = cache_entry->CopyCache(); - } - ScriptCompiler::Source source(source_text, origin, cached_data); - ScriptCompiler::CompileOptions options; - if (source.GetCachedData() == nullptr) { - options = ScriptCompiler::kNoCompileOptions; - } else { - options = ScriptCompiler::kConsumeCodeCache; - } - if (!ScriptCompiler::CompileModule(isolate, &source, options) + bool cache_rejected = false; + if (!CompileSourceTextModule(realm, + source_text, + url, + line_offset, + column_offset, + host_defined_options, + user_cached_data, + &cache_rejected) .ToLocal(&module)) { if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); @@ -291,18 +284,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { return; } - bool cache_rejected = false; - if (options == ScriptCompiler::kConsumeCodeCache) { - cache_rejected = source.GetCachedData()->rejected; - } - - if (cache_entry != nullptr) { - realm->env()->compile_cache_handler()->MaybeSave( - cache_entry, module, cache_rejected); - } - - // If the cache comes from builtin compile cache, fail silently. - if (cache_rejected && used_cache_from_user) { + if (user_cached_data.has_value() && user_cached_data.value() != nullptr && + cache_rejected) { THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( realm, "cachedData buffer was rejected"); try_catch.ReThrow(); @@ -345,6 +328,71 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(that); } +MaybeLocal ModuleWrap::CompileSourceTextModule( + Realm* realm, + Local source_text, + Local url, + int line_offset, + int column_offset, + Local host_defined_options, + std::optional user_cached_data, + bool* cache_rejected) { + Isolate* isolate = realm->isolate(); + EscapableHandleScope scope(isolate); + ScriptOrigin origin(isolate, + url, + line_offset, + column_offset, + true, // is cross origin + -1, // script id + Local(), // source map URL + false, // is opaque (?) + false, // is WASM + true, // is ES Module + host_defined_options); + ScriptCompiler::CachedData* cached_data = nullptr; + CompileCacheEntry* cache_entry = nullptr; + // When compiling for the default loader, user_cached_data is std::nullptr. + // When compiling for vm.Module, it's either nullptr or a pointer to the + // cached data. + if (user_cached_data.has_value()) { + cached_data = user_cached_data.value(); + } else if (realm->env()->use_compile_cache()) { + cache_entry = realm->env()->compile_cache_handler()->GetOrInsert( + source_text, url, CachedCodeType::kESM); + } + + if (cache_entry != nullptr && cache_entry->cache != nullptr) { + // source will take ownership of cached_data. + cached_data = cache_entry->CopyCache(); + } + + ScriptCompiler::Source source(source_text, origin, cached_data); + ScriptCompiler::CompileOptions options; + if (cached_data == nullptr) { + options = ScriptCompiler::kNoCompileOptions; + } else { + options = ScriptCompiler::kConsumeCodeCache; + } + + Local module; + if (!ScriptCompiler::CompileModule(isolate, &source, options) + .ToLocal(&module)) { + return scope.EscapeMaybe(MaybeLocal()); + } + + if (options == ScriptCompiler::kConsumeCodeCache) { + *cache_rejected = source.GetCachedData()->rejected; + } + + if (cache_entry != nullptr) { + realm->env()->compile_cache_handler()->MaybeSave( + cache_entry, module, *cache_rejected); + } + + return scope.Escape(module); +} + static Local createImportAttributesContainer( Realm* realm, Isolate* isolate, diff --git a/src/module_wrap.h b/src/module_wrap.h index 7c40ef08ea00f8..bd50bce8ad2add 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -3,10 +3,12 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include +#include #include +#include #include #include "base_object.h" +#include "v8-script.h" namespace node { @@ -69,6 +71,23 @@ class ModuleWrap : public BaseObject { return true; } + static v8::Local GetHostDefinedOptions( + v8::Isolate* isolate, v8::Local symbol); + + // When user_cached_data is not std::nullopt, use the code cache if it's not + // nullptr, otherwise don't use code cache. + // TODO(joyeecheung): when it is std::nullopt, use on-disk cache + // See: https://github.com/nodejs/node/issues/47472 + static v8::MaybeLocal CompileSourceTextModule( + Realm* realm, + v8::Local source_text, + v8::Local url, + int line_offset, + int column_offset, + v8::Local host_defined_options, + std::optional user_cached_data, + bool* cache_rejected); + private: ModuleWrap(Realm* realm, v8::Local object, diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 280607617edcd7..733cee2cae2815 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -344,16 +344,12 @@ void ContextifyContext::CreatePerIsolateProperties( Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "makeContext", MakeContext); SetMethod(isolate, target, "compileFunction", CompileFunction); - SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax); - SetMethod(isolate, target, "shouldRetryAsESM", ShouldRetryAsESM); } void ContextifyContext::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(MakeContext); registry->Register(CompileFunction); - registry->Register(ContainsModuleSyntax); - registry->Register(ShouldRetryAsESM); registry->Register(PropertyGetterCallback); registry->Register(PropertySetterCallback); registry->Register(PropertyDescriptorCallback); @@ -1159,15 +1155,6 @@ ContextifyScript::ContextifyScript(Environment* env, Local object) ContextifyScript::~ContextifyScript() {} -static Local GetHostDefinedOptions(Isolate* isolate, - Local id_symbol) { - Local host_defined_options = - PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); - host_defined_options->Set( - isolate, loader::HostDefinedOptions::kID, id_symbol); - return host_defined_options; -} - void ContextifyContext::CompileFunction( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1241,16 +1228,27 @@ void ContextifyContext::CompileFunction( } Local host_defined_options = - GetHostDefinedOptions(isolate, id_symbol); - ScriptCompiler::Source source = - GetCommonJSSourceInstance(isolate, - code, - filename, - line_offset, - column_offset, - host_defined_options, - cached_data); - ScriptCompiler::CompileOptions options = GetCompileOptions(source); + loader::ModuleWrap::GetHostDefinedOptions(isolate, id_symbol); + + ScriptOrigin origin(isolate, + filename, + line_offset, // line offset + column_offset, // column offset + true, // is cross origin + -1, // script id + Local(), // source map URL + false, // is opaque (?) + false, // is WASM + false, // is ES Module + host_defined_options); + ScriptCompiler::Source source(code, origin, cached_data); + + ScriptCompiler::CompileOptions options; + if (source.GetCachedData() != nullptr) { + options = ScriptCompiler::kConsumeCodeCache; + } else { + options = ScriptCompiler::kNoCompileOptions; + } Context::Scope scope(parsing_context); @@ -1298,39 +1296,6 @@ void ContextifyContext::CompileFunction( args.GetReturnValue().Set(result); } -ScriptCompiler::Source ContextifyContext::GetCommonJSSourceInstance( - Isolate* isolate, - Local code, - Local filename, - int line_offset, - int column_offset, - Local host_defined_options, - ScriptCompiler::CachedData* cached_data) { - ScriptOrigin origin(isolate, - filename, - line_offset, // line offset - column_offset, // column offset - true, // is cross origin - -1, // script id - Local(), // source map URL - false, // is opaque (?) - false, // is WASM - false, // is ES Module - host_defined_options); - return ScriptCompiler::Source(code, origin, cached_data); -} - -ScriptCompiler::CompileOptions ContextifyContext::GetCompileOptions( - const ScriptCompiler::Source& source) { - ScriptCompiler::CompileOptions options; - if (source.GetCachedData() != nullptr) { - options = ScriptCompiler::kConsumeCodeCache; - } else { - options = ScriptCompiler::kNoCompileOptions; - } - return options; -} - static std::vector> GetCJSParameters(IsolateData* data) { return { data->exports_string(), @@ -1436,160 +1401,17 @@ static std::vector throws_only_in_cjs_error_messages = { "await is only valid in async functions and " "the top level bodies of modules"}; -void ContextifyContext::ContainsModuleSyntax( - const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); - Local context = env->context(); - - if (args.Length() == 0) { - return THROW_ERR_MISSING_ARGS( - env, "containsModuleSyntax needs at least 1 argument"); - } - - // Argument 1: source code - CHECK(args[0]->IsString()); - auto code = args[0].As(); - - // Argument 2: filename; if undefined, use empty string - Local filename = String::Empty(isolate); - if (!args[1]->IsUndefined()) { - CHECK(args[1]->IsString()); - filename = args[1].As(); - } - - // TODO(geoffreybooth): Centralize this rather than matching the logic in - // cjs/loader.js and translators.js - Local script_id = String::Concat( - isolate, String::NewFromUtf8(isolate, "cjs:").ToLocalChecked(), filename); - Local id_symbol = Symbol::New(isolate, script_id); - - Local host_defined_options = - GetHostDefinedOptions(isolate, id_symbol); - ScriptCompiler::Source source = GetCommonJSSourceInstance( - isolate, code, filename, 0, 0, host_defined_options, nullptr); - ScriptCompiler::CompileOptions options = GetCompileOptions(source); - - std::vector> params = GetCJSParameters(env->isolate_data()); - - TryCatchScope try_catch(env); - ShouldNotAbortOnUncaughtScope no_abort_scope(env); - - ContextifyContext::CompileFunctionAndCacheResult(env, - context, - &source, - params, - std::vector>(), - options, - true, - id_symbol, - try_catch); - - bool should_retry_as_esm = false; - if (try_catch.HasCaught() && !try_catch.HasTerminated()) { - should_retry_as_esm = - ContextifyContext::ShouldRetryAsESMInternal(env, code); - } - args.GetReturnValue().Set(should_retry_as_esm); -} - -void ContextifyContext::ShouldRetryAsESM( - const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK_EQ(args.Length(), 1); // code - - // Argument 1: source code - Local code; - CHECK(args[0]->IsString()); - code = args[0].As(); - - bool should_retry_as_esm = - ContextifyContext::ShouldRetryAsESMInternal(env, code); - - args.GetReturnValue().Set(should_retry_as_esm); -} - -bool ContextifyContext::ShouldRetryAsESMInternal(Environment* env, - Local code) { - Isolate* isolate = env->isolate(); - - Local script_id = - FIXED_ONE_BYTE_STRING(isolate, "[retry_as_esm_check]"); - Local id_symbol = Symbol::New(isolate, script_id); - - Local host_defined_options = - GetHostDefinedOptions(isolate, id_symbol); - ScriptCompiler::Source source = - GetCommonJSSourceInstance(isolate, - code, - script_id, // filename - 0, // line offset - 0, // column offset - host_defined_options, - nullptr); // cached_data - - TryCatchScope try_catch(env); - ShouldNotAbortOnUncaughtScope no_abort_scope(env); - - // Try parsing where instead of the CommonJS wrapper we use an async function - // wrapper. If the parse succeeds, then any CommonJS parse error for this - // module was caused by either a top-level declaration of one of the CommonJS - // module variables, or a top-level `await`. - code = String::Concat( - isolate, FIXED_ONE_BYTE_STRING(isolate, "(async function() {"), code); - code = String::Concat(isolate, code, FIXED_ONE_BYTE_STRING(isolate, "})();")); - - ScriptCompiler::Source wrapped_source = GetCommonJSSourceInstance( - isolate, code, script_id, 0, 0, host_defined_options, nullptr); - - Local context = env->context(); - std::vector> params = GetCJSParameters(env->isolate_data()); - USE(ScriptCompiler::CompileFunction( - context, - &wrapped_source, - params.size(), - params.data(), - 0, - nullptr, - ScriptCompiler::kNoCompileOptions, - v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason)); - - if (!try_catch.HasTerminated()) { - if (try_catch.HasCaught()) { - // If on the second parse an error is thrown by ESM syntax, then - // what happened was that the user had top-level `await` or a - // top-level declaration of one of the CommonJS module variables - // above the first `import` or `export`. - Utf8Value message_value(env->isolate(), try_catch.Message()->Get()); - auto message_view = message_value.ToStringView(); - for (const auto& error_message : esm_syntax_error_messages) { - if (message_view.find(error_message) != std::string_view::npos) { - return true; - } - } - } else { - // No errors thrown in the second parse, so most likely the error - // was caused by a top-level `await` or a top-level declaration of - // one of the CommonJS module variables. - return true; - } - } - return false; -} - -static void CompileFunctionForCJSLoader( - const FunctionCallbackInfo& args) { - CHECK(args[0]->IsString()); - CHECK(args[1]->IsString()); - Local code = args[0].As(); - Local filename = args[1].As(); - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - Environment* env = Environment::GetCurrent(context); +static MaybeLocal CompileFunctionForCJSLoader(Environment* env, + Local context, + Local code, + Local filename, + bool* cache_rejected) { + Isolate* isolate = context->GetIsolate(); + EscapableHandleScope scope(isolate); Local symbol = env->vm_dynamic_import_default_internal(); - Local hdo = GetHostDefinedOptions(isolate, symbol); + Local hdo = + loader::ModuleWrap::GetHostDefinedOptions(isolate, symbol); ScriptOrigin origin(isolate, filename, 0, // line offset @@ -1636,10 +1458,7 @@ static void CompileFunctionForCJSLoader( options = ScriptCompiler::kConsumeCodeCache; } - TryCatchScope try_catch(env); - std::vector> params = GetCJSParameters(env->isolate_data()); - MaybeLocal maybe_fn = ScriptCompiler::CompileFunction( context, &source, @@ -1648,26 +1467,45 @@ static void CompileFunctionForCJSLoader( 0, /* context extensions size */ nullptr, /* context extensions data */ // TODO(joyeecheung): allow optional eager compilation. - options, - v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason); + options); Local fn; if (!maybe_fn.ToLocal(&fn)) { - if (try_catch.HasCaught() && !try_catch.HasTerminated()) { - errors::DecorateErrorStack(env, try_catch); - if (!try_catch.HasTerminated()) { - try_catch.ReThrow(); - } - return; - } + return scope.EscapeMaybe(MaybeLocal()); } - bool cache_rejected = false; if (options == ScriptCompiler::kConsumeCodeCache) { - cache_rejected = source.GetCachedData()->rejected; + *cache_rejected = source.GetCachedData()->rejected; } if (cache_entry != nullptr) { - env->compile_cache_handler()->MaybeSave(cache_entry, fn, cache_rejected); + env->compile_cache_handler()->MaybeSave(cache_entry, fn, *cache_rejected); + } + return scope.Escape(fn); +} + +static void CompileFunctionForCJSLoader( + const FunctionCallbackInfo& args) { + CHECK(args[0]->IsString()); + CHECK(args[1]->IsString()); + Local code = args[0].As(); + Local filename = args[1].As(); + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Environment* env = Environment::GetCurrent(context); + + bool cache_rejected = false; + Local fn; + { + TryCatchScope try_catch(env); + if (!CompileFunctionForCJSLoader( + env, context, code, filename, &cache_rejected) + .ToLocal(&fn)) { + CHECK(try_catch.HasCaught()); + CHECK(!try_catch.HasTerminated()); + errors::DecorateErrorStack(env, try_catch); + try_catch.ReThrow(); + return; + } } std::vector> names = { @@ -1685,6 +1523,108 @@ static void CompileFunctionForCJSLoader( args.GetReturnValue().Set(result); } +static bool ShouldRetryAsESM(Realm* realm, + Local message, + Local code, + Local resource_name) { + Isolate* isolate = realm->isolate(); + + Utf8Value message_value(isolate, message); + auto message_view = message_value.ToStringView(); + + // These indicates that the file contains syntaxes that are only valid in + // ESM. So it must be true. + for (const auto& error_message : esm_syntax_error_messages) { + if (message_view.find(error_message) != std::string_view::npos) { + return true; + } + } + + // Check if the error message is allowed in ESM but not in CommonJS. If it + // is the case, let's check if file can be compiled as ESM. + bool maybe_valid_in_esm = false; + for (const auto& error_message : throws_only_in_cjs_error_messages) { + if (message_view.find(error_message) != std::string_view::npos) { + maybe_valid_in_esm = true; + break; + } + } + if (!maybe_valid_in_esm) { + return false; + } + + bool cache_rejected = false; + TryCatchScope try_catch(realm->env()); + ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); + Local module; + Local hdo = loader::ModuleWrap::GetHostDefinedOptions( + isolate, realm->isolate_data()->source_text_module_default_hdo()); + if (loader::ModuleWrap::CompileSourceTextModule( + realm, code, resource_name, 0, 0, hdo, nullptr, &cache_rejected) + .ToLocal(&module)) { + return true; + } + + return false; +} + +static void ShouldRetryAsESM(const FunctionCallbackInfo& args) { + Realm* realm = Realm::GetCurrent(args); + + CHECK_EQ(args.Length(), 3); // message, code, resource_name + CHECK(args[0]->IsString()); + Local message = args[0].As(); + CHECK(args[1]->IsString()); + Local code = args[1].As(); + CHECK(args[2]->IsString()); + Local resource_name = args[2].As(); + + args.GetReturnValue().Set( + ShouldRetryAsESM(realm, message, code, resource_name)); +} + +static void ContainsModuleSyntax(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Realm* realm = Realm::GetCurrent(context); + Environment* env = realm->env(); + + CHECK_GE(args.Length(), 2); + + // Argument 1: source code + CHECK(args[0]->IsString()); + Local code = args[0].As(); + + // Argument 2: filename + CHECK(args[1]->IsString()); + Local filename = args[1].As(); + + // Argument 2: resource name (URL for ES module). + Local resource_name = filename; + if (args[2]->IsString()) { + resource_name = args[2].As(); + } + + bool cache_rejected = false; + Local message; + { + Local fn; + TryCatchScope try_catch(env); + ShouldNotAbortOnUncaughtScope no_abort_scope(env); + if (CompileFunctionForCJSLoader( + env, context, code, filename, &cache_rejected) + .ToLocal(&fn)) { + args.GetReturnValue().Set(false); + return; + } + CHECK(try_catch.HasCaught()); + message = try_catch.Message()->Get(); + } + + bool result = ShouldRetryAsESM(realm, message, code, resource_name); + args.GetReturnValue().Set(result); +} + static void StartSigintWatchdog(const FunctionCallbackInfo& args) { int ret = SigintWatchdogHelper::GetInstance()->Start(); args.GetReturnValue().Set(ret == 0); @@ -1741,6 +1681,9 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, target, "compileFunctionForCJSLoader", CompileFunctionForCJSLoader); + + SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax); + SetMethod(isolate, target, "shouldRetryAsESM", ShouldRetryAsESM); } static void CreatePerContextProperties(Local target, @@ -1753,7 +1696,6 @@ static void CreatePerContextProperties(Local target, Local constants = Object::New(env->isolate()); Local measure_memory = Object::New(env->isolate()); Local memory_execution = Object::New(env->isolate()); - Local syntax_detection_errors = Object::New(env->isolate()); { Local memory_mode = Object::New(env->isolate()); @@ -1774,25 +1716,6 @@ static void CreatePerContextProperties(Local target, READONLY_PROPERTY(constants, "measureMemory", measure_memory); - { - Local esm_syntax_error_messages_array = - ToV8Value(context, esm_syntax_error_messages).ToLocalChecked(); - READONLY_PROPERTY(syntax_detection_errors, - "esmSyntaxErrorMessages", - esm_syntax_error_messages_array); - } - - { - Local throws_only_in_cjs_error_messages_array = - ToV8Value(context, throws_only_in_cjs_error_messages).ToLocalChecked(); - READONLY_PROPERTY(syntax_detection_errors, - "throwsOnlyInCommonJSErrorMessages", - throws_only_in_cjs_error_messages_array); - } - - READONLY_PROPERTY( - constants, "syntaxDetectionErrors", syntax_detection_errors); - target->Set(context, env->constants_string(), constants).Check(); } @@ -1805,6 +1728,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(StopSigintWatchdog); registry->Register(WatchdogHasPendingSigint); registry->Register(MeasureMemory); + registry->Register(ContainsModuleSyntax); + registry->Register(ShouldRetryAsESM); } } // namespace contextify } // namespace node diff --git a/src/node_contextify.h b/src/node_contextify.h index 66a9f765fe9212..b5cc4e546f4ab7 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -94,21 +94,6 @@ class ContextifyContext : public BaseObject { bool produce_cached_data, v8::Local id_symbol, const errors::TryCatchScope& try_catch); - static v8::ScriptCompiler::Source GetCommonJSSourceInstance( - v8::Isolate* isolate, - v8::Local code, - v8::Local filename, - int line_offset, - int column_offset, - v8::Local host_defined_options, - v8::ScriptCompiler::CachedData* cached_data); - static v8::ScriptCompiler::CompileOptions GetCompileOptions( - const v8::ScriptCompiler::Source& source); - static void ContainsModuleSyntax( - const v8::FunctionCallbackInfo& args); - static void ShouldRetryAsESM(const v8::FunctionCallbackInfo& args); - static bool ShouldRetryAsESMInternal(Environment* env, - v8::Local code); static void WeakCallback( const v8::WeakCallbackInfo& data); static void PropertyGetterCallback(