This aligns njs with QuickJS and most other JS engines.
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;
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) {
njs_set_object(retval, error);
+ njs_error_stack_attach(vm, *retval, 1);
+
return NJS_OK;
}
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;
}
njs_set_object(&argument, error);
+
+ njs_error_stack_attach(vm, argument, 1);
}
ret = njs_function_call(vm, njs_function(&pargs->capability->resolve),
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);
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 ( ;; ) {
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]") },
{ 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
" 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 <anonymous> (: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") },
--- /dev/null
+/*---
+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);
--- /dev/null
+/*---
+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);
--- /dev/null
+/*---
+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);
--- /dev/null
+/*---
+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);
--- /dev/null
+/*---
+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);
--- /dev/null
+/*---
+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);
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()