From: Dmitry Volyntsev Date: Fri, 30 Jan 2026 01:18:31 +0000 (-0800) Subject: Attach stack in error constructors. X-Git-Tag: 0.9.6~23 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=05eb4436fe09800af1a6ea83d91b734d1111fffc;p=njs.git Attach stack in error constructors. This aligns njs with QuickJS and most other JS engines. --- diff --git a/src/njs_error.c b/src/njs_error.c index 13ed32aa..eaa8c655 100644 --- a/src/njs_error.c +++ b/src/njs_error.c @@ -68,7 +68,7 @@ njs_error_fmt_new(njs_vm_t *vm, njs_value_t *dst, njs_object_type_t type, void -njs_error_stack_attach(njs_vm_t *vm, njs_value_t value) +njs_error_stack_attach(njs_vm_t *vm, njs_value_t value, njs_uint_t skip) { size_t count; uint32_t line, prev_line; @@ -96,6 +96,11 @@ njs_error_stack_attach(njs_vm_t *vm, njs_value_t value) prev_name = njs_str_value(""); for (frame = vm->top_frame; frame != NULL; frame = frame->previous) { + if (skip != 0) { + skip--; + continue; + } + function = frame->native ? frame->function : NULL; if (function != NULL && function->bound != NULL) { @@ -380,6 +385,8 @@ njs_error_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_set_object(retval, error); + njs_error_stack_attach(vm, *retval, 1); + return NJS_OK; } diff --git a/src/njs_error.h b/src/njs_error.h index 4660660b..9c42b14a 100644 --- a/src/njs_error.h +++ b/src/njs_error.h @@ -43,7 +43,7 @@ njs_object_t *njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, njs_int_t njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error); njs_int_t njs_error_stack(njs_vm_t *vm, njs_value_t *value, njs_value_t *stack); -void njs_error_stack_attach(njs_vm_t *vm, njs_value_t value); +void njs_error_stack_attach(njs_vm_t *vm, njs_value_t value, njs_uint_t skip); extern const njs_object_type_init_t njs_error_type_init; diff --git a/src/njs_promise.c b/src/njs_promise.c index 53a8c4e2..016c6859 100644 --- a/src/njs_promise.c +++ b/src/njs_promise.c @@ -1287,6 +1287,8 @@ njs_promise_perform_all(njs_vm_t *vm, njs_value_t *iterator, } njs_set_object(&argument, error); + + njs_error_stack_attach(vm, argument, 1); } ret = njs_function_call(vm, njs_function(&pargs->capability->resolve), @@ -1664,6 +1666,8 @@ njs_promise_any_reject_element_functions(njs_vm_t *vm, njs_value_t *args, njs_set_object(&argument, error); + njs_error_stack_attach(vm, argument, 1); + return njs_function_call(vm, njs_function(&context->capability->reject), &njs_value_undefined, &argument, 1, retval); diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 81d3bdd7..6d991042 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -1854,7 +1854,7 @@ error: if (njs_is_error(&vm->exception)) { vm->active_frame->native.pc = pc; - njs_error_stack_attach(vm, vm->exception); + njs_error_stack_attach(vm, vm->exception, 0); } for ( ;; ) { diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index ddb92c04..215bea9e 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -21403,7 +21403,7 @@ static njs_unit_test_t njs_shell_test[] = njs_str("9") }, { njs_str("var e = Error(); e.name = {}; e" ENTER), - njs_str("[object Object]") }, + njs_str("[object Object]\n at main (:1)\n") }, { njs_str("var a = []; Object.defineProperty(a, 'b', {enumerable: true, get: Object}); a" ENTER), njs_str("[\n b: '[Getter]'\n]") }, @@ -21411,12 +21411,12 @@ static njs_unit_test_t njs_shell_test[] = { njs_str("var e = Error()" ENTER "Object.defineProperty(e, 'message', { configurable: true, set: Object })" ENTER "delete e.message; e" ENTER), - njs_str("Error") }, + njs_str("Error\n at main (:1)\n") }, { njs_str("var e = Error()" ENTER "Object.defineProperty(e, 'message', { configurable: true, get(){ return 'foo'} })" ENTER "e" ENTER), - njs_str("Error: foo") }, + njs_str("Error: foo\n at main (:1)\n") }, { njs_str("function f() {};" ENTER "Object.defineProperty(f, 'name', { get() {void(0)} })" ENTER @@ -21676,6 +21676,13 @@ static njs_unit_test_t njs_backtraces_test[] = " at every (native)\n" " at main (:1)\n") }, + { njs_str("var p = new Promise((_, reject) => { reject(new Error('oops'));});" + "p.catch((e) => { $r.retval(e.stack) });"), + njs_str("Error: oops\n" + " at (:1)\n" + " at Promise (native)\n" + " at main (:1)\n") }, + { njs_str("var e = new Error('oops'); e.stack = 123; e.stack"), njs_str("123") }, diff --git a/test/js/error_stack_created.t.js b/test/js/error_stack_created.t.js new file mode 100644 index 00000000..4bfa3ad1 --- /dev/null +++ b/test/js/error_stack_created.t.js @@ -0,0 +1,21 @@ +/*--- +includes: [] +flags: [] +---*/ + +// Error.stack should be available when error is created (not just thrown) +// This matches V8, SpiderMonkey, and QuickJS behavior + +function inner() { + return new Error("test error"); +} + +function outer() { + return inner(); +} + +var e = outer(); + +assert.sameValue(typeof e.stack, 'string'); +assert.sameValue(e.stack.includes('inner'), true); +assert.sameValue(e.stack.includes('outer'), true); diff --git a/test/js/error_stack_promise_all.t.js b/test/js/error_stack_promise_all.t.js new file mode 100644 index 00000000..147943c2 --- /dev/null +++ b/test/js/error_stack_promise_all.t.js @@ -0,0 +1,18 @@ +/*--- +includes: [] +flags: [async] +---*/ + +// Error.stack should be available in Promise.all rejection + +var p1 = new Promise((resolve, reject) => { + reject(new Error("promise failed")); +}); +var p2 = Promise.resolve("ok"); + +Promise.all([p1, p2]) +.then(v => $DONOTEVALUATE()) +.catch(e => { + assert.sameValue(typeof e.stack, 'string'); +}) +.then($DONE, $DONE); diff --git a/test/js/error_stack_promise_any_inner.t.js b/test/js/error_stack_promise_any_inner.t.js new file mode 100644 index 00000000..abbe5e07 --- /dev/null +++ b/test/js/error_stack_promise_any_inner.t.js @@ -0,0 +1,19 @@ +/*--- +includes: [] +flags: [async] +---*/ + +// Inner errors in AggregateError.errors should have stack traces + +var p1 = Promise.reject(new Error("inner error 1")); +var p2 = Promise.reject(new Error("inner error 2")); + +Promise.any([p1, p2]) +.then(v => $DONOTEVALUATE()) +.catch(e => { + assert.sameValue(e.name, 'AggregateError'); + assert.sameValue(e.errors.length, 2); + assert.sameValue(typeof e.errors[0].stack, 'string'); + assert.sameValue(typeof e.errors[1].stack, 'string'); +}) +.then($DONE, $DONE); diff --git a/test/js/error_stack_promise_race.t.js b/test/js/error_stack_promise_race.t.js new file mode 100644 index 00000000..6b8594fa --- /dev/null +++ b/test/js/error_stack_promise_race.t.js @@ -0,0 +1,17 @@ +/*--- +includes: [] +flags: [async] +---*/ + +// Error.stack should be available in Promise.race rejection + +var p1 = new Promise((resolve, reject) => { + reject(new Error("race failed")); +}); + +Promise.race([p1]) +.then(v => $DONOTEVALUATE()) +.catch(e => { + assert.sameValue(typeof e.stack, 'string'); +}) +.then($DONE, $DONE); diff --git a/test/js/error_stack_promise_reject.t.js b/test/js/error_stack_promise_reject.t.js new file mode 100644 index 00000000..f237fb70 --- /dev/null +++ b/test/js/error_stack_promise_reject.t.js @@ -0,0 +1,19 @@ +/*--- +includes: [] +flags: [async] +---*/ + +// Error.stack should be available when error is passed to reject() + +function rejectWithError() { + return new Promise((resolve, reject) => { + reject(new Error("rejected")); + }); +} + +rejectWithError() +.catch(e => { + assert.sameValue(typeof e.stack, 'string'); + assert.sameValue(e.stack.includes('Error'), true); +}) +.then($DONE, $DONE); diff --git a/test/js/error_stack_type_error.t.js b/test/js/error_stack_type_error.t.js new file mode 100644 index 00000000..431b1015 --- /dev/null +++ b/test/js/error_stack_type_error.t.js @@ -0,0 +1,16 @@ +/*--- +includes: [] +flags: [] +---*/ + +// TypeError.stack should be available when error is created + +function createTypeError() { + return new TypeError("type error message"); +} + +var e = createTypeError(); + +assert.sameValue(typeof e.stack, 'string'); +assert.sameValue(e.stack.includes('TypeError'), true); +assert.sameValue(e.stack.includes('createTypeError'), true); diff --git a/test/shell_test_njs.exp b/test/shell_test_njs.exp index 6dea0442..35b82339 100644 --- a/test/shell_test_njs.exp +++ b/test/shell_test_njs.exp @@ -90,7 +90,7 @@ njs_test { njs_test { {"[InternalError(),TypeError('msg'), new RegExp(), /^undef$/m, new Date(0)]\r\n" - "\\\[\r\n InternalError,\r\n TypeError: msg,\r\n /(?:)/,\r\n /^undef$/m,\r\n 1970-01-01T00:00:00.000Z\r\n]"} + "\\\[\r\n InternalError\r\n at main \\(shell:1\\)\r\n,\r\n TypeError: msg\r\n at main \\(shell:1\\)\r\n,\r\n /(?:)/,\r\n /^undef$/m,\r\n 1970-01-01T00:00:00.000Z\r\n]"} } # dumper excapes special characters as JSON.stringify()