diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 22e77d8bff70c0..8cab0808eaa898 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -1338,15 +1338,8 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) { return result; } catch (err) { if (process.mainModule === cjsModuleInstance) { - if (getOptionValue('--experimental-detect-module')) { - // For the main entry point, cache the source to potentially retry as ESM. - module.exports.entryPointSource = content; - } else { - // We only enrich the error (print a warning) if we're sure we're going to for-sure throw it; so if we're - // retrying as ESM, wait until we know whether we're going to retry before calling `enrichCJSError`. - const { enrichCJSError } = require('internal/modules/esm/translators'); - enrichCJSError(err, content, filename); - } + // For the main entry point, cache the source to potentially retry as ESM; or to decorate the error. + module.exports.entryPointSource = content; } throw err; } diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index c06c8cc72cc9f4..8cf0e8fe910b16 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -15,6 +15,7 @@ const { hasUncaughtExceptionCaptureCallback, } = require('internal/process/execution'); const { + tryCatchRethrow, triggerUncaughtException, } = internalBinding('errors'); const { @@ -166,26 +167,31 @@ function executeUserEntryPoint(main = process.argv[1]) { if (!useESMLoader) { const cjsLoader = require('internal/modules/cjs/loader'); const { Module } = cjsLoader; - if (getOptionValue('--experimental-detect-module')) { - try { - // Module._load is the monkey-patchable CJS module loader. - Module._load(main, null, true); - } catch (error) { - const source = cjsLoader.entryPointSource; + + tryCatchRethrow(() => { + // Module._load is the monkey-patchable CJS module loader. + Module._load(main, null, true); + }, (error) => { + const source = cjsLoader.entryPointSource; + if (getOptionValue('--experimental-detect-module')) { + if (!source) { + return true; // Rethrow original exception. + } 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 (!retryAsESM) { + } + + if (!retryAsESM) { + if (source) { const { enrichCJSError } = require('internal/modules/esm/translators'); enrichCJSError(error, source, resolvedMain); - throw error; } + return true; // Rethrow original exception. } - } else { // `--experimental-detect-module` is not passed - Module._load(main, null, true); - } + }); } if (useESMLoader || retryAsESM) { diff --git a/src/node_errors.cc b/src/node_errors.cc index ff091fd20d915b..beb28346397050 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -36,6 +36,39 @@ using v8::String; using v8::Undefined; using v8::Value; +// Takes two JS callbacks, and calls the first one in a TryCatch scope. +// If the first one throws, call the second one with the exception. +// If the second one returns true, rethrow the original exception. +// This is to keep the location of the original throw in JS. +static void TryCatchRethrow(const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsFunction()); + CHECK(args[1]->IsFunction()); + + Local try_fn = args[0].As(); + Local catch_fn = args[1].As(); + + Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(isolate); + Local context = isolate->GetCurrentContext(); + + TryCatchScope try_catch_scope(env); + USE(try_fn->Call(context, Undefined(isolate), 0, nullptr)); + + if (try_catch_scope.HasCaught()) { + Local exception = try_catch_scope.Exception(); + Local argv[] = {exception}; + Local result; + if (!catch_fn->Call(context, Undefined(isolate), 1, argv) + .ToLocal(&result)) { + return; + } + if (result->IsTrue()) { + try_catch_scope.ReThrow(); + } + } +} + bool IsExceptionDecorated(Environment* env, Local er) { if (!er.IsEmpty() && er->IsObject()) { Local err_obj = er.As(); @@ -1089,6 +1122,7 @@ static void TriggerUncaughtException(const FunctionCallbackInfo& args) { } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(TryCatchRethrow); registry->Register(SetPrepareStackTraceCallback); registry->Register(SetGetSourceMapErrorSource); registry->Register(SetSourceMapsEnabled); @@ -1102,6 +1136,7 @@ void Initialize(Local target, Local unused, Local context, void* priv) { + SetMethod(context, target, "tryCatchRethrow", TryCatchRethrow); SetMethod(context, target, "setPrepareStackTraceCallback", diff --git a/test/es-module/test-esm-cjs-load-error-note.mjs b/test/es-module/test-esm-cjs-load-error-note.mjs index 00ff5582c71bda..16f9ac0708fd1a 100644 --- a/test/es-module/test-esm-cjs-load-error-note.mjs +++ b/test/es-module/test-esm-cjs-load-error-note.mjs @@ -79,7 +79,7 @@ describe('ESM: Errors for unexpected exports', { concurrency: true }, () => { }, ] ) { - it(`should ${includeNote ? '' : 'NOT'} include note`, async () => { + it(`should${includeNote ? '' : ' NOT'} include note`, async () => { const { code, stderr } = await spawnPromisified(execPath, [filePath]); assert.strictEqual(code, 1); diff --git a/test/fixtures/console/console.snapshot b/test/fixtures/console/console.snapshot index 9aabbe5c5f275a..4f1cb254811b6d 100644 --- a/test/fixtures/console/console.snapshot +++ b/test/fixtures/console/console.snapshot @@ -6,3 +6,4 @@ Trace: foo at * at * at * + at * diff --git a/test/fixtures/errors/force_colors.snapshot b/test/fixtures/errors/force_colors.snapshot index 3624d9f0804545..21410d492db861 100644 --- a/test/fixtures/errors/force_colors.snapshot +++ b/test/fixtures/errors/force_colors.snapshot @@ -10,5 +10,6 @@ Error: Should include grayed stack trace  at *  at *  at * + at * Node.js * diff --git a/test/fixtures/errors/promise_always_throw_unhandled.snapshot b/test/fixtures/errors/promise_always_throw_unhandled.snapshot index c97540f5499f87..cf42c69efdc372 100644 --- a/test/fixtures/errors/promise_always_throw_unhandled.snapshot +++ b/test/fixtures/errors/promise_always_throw_unhandled.snapshot @@ -12,5 +12,6 @@ Error: One at * at * at * + at * Node.js * diff --git a/test/fixtures/errors/promise_unhandled_warn_with_error.snapshot b/test/fixtures/errors/promise_unhandled_warn_with_error.snapshot index d7f1aa2f72007f..48e5da7c9aded5 100644 --- a/test/fixtures/errors/promise_unhandled_warn_with_error.snapshot +++ b/test/fixtures/errors/promise_unhandled_warn_with_error.snapshot @@ -6,5 +6,6 @@ at * at * at * + at * (Use `node --trace-warnings ...` to show where the warning was created) (node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https:*nodejs.org*api*cli.html#cli_unhandled_rejections_mode). (rejection id: 1) diff --git a/test/fixtures/errors/unhandled_promise_trace_warnings.snapshot b/test/fixtures/errors/unhandled_promise_trace_warnings.snapshot index 1ed082d0736851..c95df6d5aa056d 100644 --- a/test/fixtures/errors/unhandled_promise_trace_warnings.snapshot +++ b/test/fixtures/errors/unhandled_promise_trace_warnings.snapshot @@ -9,6 +9,7 @@ at * at * at * + at * (node:*) Error: This was rejected at * at * @@ -17,6 +18,7 @@ at * at * at * + at * (node:*) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) at * at * diff --git a/test/fixtures/test-runner/output/abort.snapshot b/test/fixtures/test-runner/output/abort.snapshot index 1b758a2314c486..be0936bd763fbb 100644 --- a/test/fixtures/test-runner/output/abort.snapshot +++ b/test/fixtures/test-runner/output/abort.snapshot @@ -135,6 +135,7 @@ not ok 2 - promise abort signal * * * + * ... # Subtest: callback timeout signal # Subtest: ok 1 @@ -272,6 +273,7 @@ not ok 4 - callback abort signal * * * + * ... 1..4 # tests 22 diff --git a/test/fixtures/test-runner/output/abort_suite.snapshot b/test/fixtures/test-runner/output/abort_suite.snapshot index 30d48d236ff4a5..b3eb5d9126db26 100644 --- a/test/fixtures/test-runner/output/abort_suite.snapshot +++ b/test/fixtures/test-runner/output/abort_suite.snapshot @@ -137,6 +137,7 @@ not ok 2 - describe abort signal * * * + * ... 1..2 # tests 9 diff --git a/test/message/assert_throws_stack.out b/test/message/assert_throws_stack.out index 06eaa906442440..652a60532c68d5 100644 --- a/test/message/assert_throws_stack.out +++ b/test/message/assert_throws_stack.out @@ -15,6 +15,7 @@ AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: at * at * at * + at * at * { generatedMessage: true, code: 'ERR_ASSERTION', diff --git a/test/message/internal_assert.out b/test/message/internal_assert.out index 197b863bf6ae69..e4f67f650f938e 100644 --- a/test/message/internal_assert.out +++ b/test/message/internal_assert.out @@ -12,6 +12,7 @@ Please open an issue with this stack trace at https://github.com/nodejs/node/iss at * at * at * + at * at * { code: 'ERR_INTERNAL_ASSERTION' } diff --git a/test/message/internal_assert_fail.out b/test/message/internal_assert_fail.out index e6895691cda9c1..6a5916401bb7f6 100644 --- a/test/message/internal_assert_fail.out +++ b/test/message/internal_assert_fail.out @@ -13,6 +13,7 @@ Please open an issue with this stack trace at https://github.com/nodejs/node/iss at * at * at * + at * at * { code: 'ERR_INTERNAL_ASSERTION' } diff --git a/test/message/util-inspect-error-cause.out b/test/message/util-inspect-error-cause.out index 73f0a673d76e1f..344ace1bc94074 100644 --- a/test/message/util-inspect-error-cause.out +++ b/test/message/util-inspect-error-cause.out @@ -5,6 +5,7 @@ Error: Number error cause at * at * at * + at * at * { [cause]: 42 } @@ -15,6 +16,7 @@ Error: Object cause at * at * at * + at * at * { [cause]: { message: 'Unique', @@ -30,6 +32,7 @@ Error: undefined cause at * at * at * + at * at * { [cause]: undefined } @@ -40,6 +43,7 @@ Error: cause that throws at * at * at * + at * at * { [cause]: [Getter] } @@ -49,7 +53,7 @@ RangeError: New Stack Frames [cause]: FoobarError: Individual message at * *[90m at *[39m - *[90m ... 4 lines matching cause stack trace ...*[39m + *[90m ... 5 lines matching cause stack trace ...*[39m *[90m at *[39m { status: *[32m'Feeling good'*[39m, extraProperties: *[32m'Yes!'*[39m, @@ -61,17 +65,18 @@ RangeError: New Stack Frames *[90m at *[39m *[90m at *[39m *[90m at *[39m + *[90m at *[39m } } Error: Stack causes at * *[90m at *[39m -*[90m ... 4 lines matching cause stack trace ...*[39m +*[90m ... 5 lines matching cause stack trace ...*[39m *[90m at *[39m { [cause]: FoobarError: Individual message at * *[90m at *[39m - *[90m ... 4 lines matching cause stack trace ...*[39m + *[90m ... 5 lines matching cause stack trace ...*[39m *[90m at *[39m { status: *[32m'Feeling good'*[39m, extraProperties: *[32m'Yes!'*[39m, @@ -83,6 +88,7 @@ Error: Stack causes *[90m at *[39m *[90m at *[39m *[90m at *[39m + *[90m at *[39m } } RangeError: New Stack Frames @@ -91,12 +97,12 @@ RangeError: New Stack Frames [cause]: Error: Stack causes at * *[90m at *[39m - *[90m ... 4 lines matching cause stack trace ...*[39m + *[90m ... 5 lines matching cause stack trace ...*[39m *[90m at *[39m { [cause]: FoobarError: Individual message at * *[90m at *[39m - *[90m ... 4 lines matching cause stack trace ...*[39m + *[90m ... 5 lines matching cause stack trace ...*[39m *[90m at *[39m { status: *[32m'Feeling good'*[39m, extraProperties: *[32m'Yes!'*[39m, @@ -108,6 +114,7 @@ RangeError: New Stack Frames *[90m at *[39m *[90m at *[39m *[90m at *[39m + *[90m at *[39m } } } @@ -117,7 +124,7 @@ RangeError: New Stack Frames [cause]: FoobarError: Individual message at * at * - ... 4 lines matching cause stack trace ... + ... 5 lines matching cause stack trace ... at * { status: 'Feeling good', extraProperties: 'Yes!', @@ -129,17 +136,18 @@ RangeError: New Stack Frames at * at * at * + at * } } Error: Stack causes at * at * - ... 4 lines matching cause stack trace ... + ... 5 lines matching cause stack trace ... at * { [cause]: FoobarError: Individual message at * at * - ... 4 lines matching cause stack trace ... + ... 5 lines matching cause stack trace ... at * status: 'Feeling good', extraProperties: 'Yes!', @@ -151,6 +159,7 @@ Error: Stack causes at * at * at * + at * } } RangeError: New Stack Frames @@ -159,12 +168,12 @@ RangeError: New Stack Frames [cause]: Error: Stack causes at * at * - ... 4 lines matching cause stack trace ... + ... 5 lines matching cause stack trace ... at * { [cause]: FoobarError: Individual message at * at * - ... 4 lines matching cause stack trace ... + ... 5 lines matching cause stack trace ... at * { status: 'Feeling good', extraProperties: 'Yes!', @@ -176,6 +185,7 @@ RangeError: New Stack Frames at * at * at * + at * } } } diff --git a/test/message/util_inspect_error.out b/test/message/util_inspect_error.out index 644ccd58831ef7..31b65eb2e2bf3c 100644 --- a/test/message/util_inspect_error.out +++ b/test/message/util_inspect_error.out @@ -8,6 +8,7 @@ at * at * at * + at * nested: { err: Error: foo @@ -19,6 +20,7 @@ at * at * at * + at * { err: Error: foo bar @@ -29,6 +31,7 @@ at * at * at * + at * nested: { err: Error: foo bar @@ -39,6 +42,7 @@ at * at * at * + at * } } { Error: foo @@ -50,4 +54,5 @@ bar at * at * at * + at * foo: 'bar' } diff --git a/test/pseudo-tty/console_colors.out b/test/pseudo-tty/console_colors.out index 006eb9edfe617d..108136956e132e 100644 --- a/test/pseudo-tty/console_colors.out +++ b/test/pseudo-tty/console_colors.out @@ -12,6 +12,7 @@ foobar [90m at *[39m [90m at *[39m [90m at *[39m +[90m at *[39m Error: Should not ever get here. at Object. [90m(*node_modules*[4m*node_modules*[24m*bar.js:*:*[90m)[39m diff --git a/test/pseudo-tty/test-fatal-error.out b/test/pseudo-tty/test-fatal-error.out index 3fe1eed39a021e..9fb8df4251a49b 100644 --- a/test/pseudo-tty/test-fatal-error.out +++ b/test/pseudo-tty/test-fatal-error.out @@ -9,6 +9,7 @@ TypeError: foobar [90m at *(node:internal*loader:*:*)[39m [90m at *(node:internal*loader:*:*)[39m [90m at *[39m +[90m at *[39m [90m at *[39m { bla: [33mtrue[39m } diff --git a/test/pseudo-tty/test-trace-sigint.out b/test/pseudo-tty/test-trace-sigint.out index 956cbafccc2d19..ae6063b74242e4 100644 --- a/test/pseudo-tty/test-trace-sigint.out +++ b/test/pseudo-tty/test-trace-sigint.out @@ -7,3 +7,4 @@ KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT` at * at * at * + at *