diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index 1fe5564545dbc8..f07f2a42b260a3 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 dd4c3924a47037..d560c0d8089e19 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'); @@ -338,30 +329,6 @@ function urlToFilename(url) { return url; } -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; -} - // Whether we have started executing any user-provided CJS code. // This is set right before we call the wrapped CJS code (not after, @@ -396,7 +363,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 cfe00865d1f22a..7a99c386fe6ded 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, } = primordials; const { getOptionValue } = require('internal/options'); @@ -155,7 +157,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; @@ -163,19 +165,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; } } @@ -186,7 +190,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 unfinished TLA, this may never resolve diff --git a/src/module_wrap.cc b/src/module_wrap.cc index eea74bed4bb8a9..4cd75447b77526 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -102,9 +102,17 @@ ModuleWrap* ModuleWrap::GetFromModule(Environment* env, return nullptr; } -// 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()); @@ -134,7 +142,7 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { int column_offset = 0; bool synthetic = args[2]->IsArray(); - + bool can_use_builtin_cache = false; Local host_defined_options = PrimitiveArray::New(isolate, HostDefinedOptions::kLength); Local id_symbol; @@ -143,9 +151,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(); @@ -153,10 +162,13 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { column_offset = args[4].As()->Value(); if (args[5]->IsSymbol()) { id_symbol = args[5].As(); + can_use_builtin_cache = + (id_symbol == + realm->isolate_data()->source_text_module_default_hdo()); } 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(), @@ -189,36 +201,34 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { module = Module::CreateSyntheticModule(isolate, url, export_names, SyntheticModuleEvaluationStepsCallback); } else { - ScriptCompiler::CachedData* cached_data = nullptr; + // When we are compiling for the default loader, this will be + // std::nullopt, and CompileSourceTextModule() should use + // on-disk cache (not present on v20.x). + std::optional user_cached_data; + if (id_symbol != + realm->isolate_data()->source_text_module_default_hdo()) { + user_cached_data = nullptr; + } if (args[5]->IsArrayBufferView()) { + 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); - 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()); @@ -231,8 +241,9 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { } return; } - if (options == ScriptCompiler::kConsumeCodeCache && - source.GetCachedData()->rejected) { + + 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(); @@ -275,6 +286,57 @@ 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; + // 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(); + } + + 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; + } + + return scope.Escape(module); +} + static Local createImportAttributesContainer( Realm* realm, Isolate* isolate, diff --git a/src/module_wrap.h b/src/module_wrap.h index 45a338b38e01c8..09da8ee43fa8b4 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 { @@ -68,6 +70,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 0a409c5086f713..7eb7335975e219 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -363,16 +363,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); @@ -1196,15 +1192,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); @@ -1278,16 +1265,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); @@ -1335,39 +1333,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(), @@ -1473,160 +1438,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 @@ -1641,7 +1463,6 @@ static void CompileFunctionForCJSLoader( ScriptCompiler::CachedData* cached_data = nullptr; #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION - bool used_cache_from_sea = false; if (sea::IsSingleExecutable()) { sea::SeaResource sea = sea::FindSingleExecutableResource(); if (sea.use_code_cache()) { @@ -1650,16 +1471,18 @@ static void CompileFunctionForCJSLoader( reinterpret_cast(data.data()), static_cast(data.size()), v8::ScriptCompiler::CachedData::BufferNotOwned); - used_cache_from_sea = true; } } #endif ScriptCompiler::Source source(code, origin, cached_data); - - TryCatchScope try_catch(env); + ScriptCompiler::CompileOptions options; + if (cached_data == nullptr) { + options = ScriptCompiler::kNoCompileOptions; + } else { + options = ScriptCompiler::kConsumeCodeCache; + } std::vector> params = GetCJSParameters(env->isolate_data()); - MaybeLocal maybe_fn = ScriptCompiler::CompileFunction( context, &source, @@ -1668,27 +1491,43 @@ static void CompileFunctionForCJSLoader( 0, /* context extensions size */ nullptr, /* context extensions data */ // TODO(joyeecheung): allow optional eager compilation. - cached_data == nullptr ? ScriptCompiler::kNoCompileOptions - : ScriptCompiler::kConsumeCodeCache, - 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()); } + if (options == ScriptCompiler::kConsumeCodeCache) { + *cache_rejected = source.GetCachedData()->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; -#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION - if (used_cache_from_sea) { - cache_rejected = source.GetCachedData()->rejected; + 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; + } } -#endif std::vector> names = { env->cached_data_rejected_string(), @@ -1705,6 +1544,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); @@ -1761,6 +1702,9 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, target, "compileFunctionForCJSLoader", CompileFunctionForCJSLoader); + + SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax); + SetMethod(isolate, target, "shouldRetryAsESM", ShouldRetryAsESM); } static void CreatePerContextProperties(Local target, @@ -1773,7 +1717,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()); @@ -1794,25 +1737,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(); } @@ -1825,6 +1749,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 ff975c8cf135e4..88b5684844b915 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -98,21 +98,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(