From: Dmitry Volyntsev Date: Mon, 17 Aug 2026 16:33:34 +0000 (-0700) Subject: QuickJS: fix empty stack in errors created by the host X-Git-Tag: 1.0.1~5 X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=0d5511c5f560e6ec30949e7d3c75527a53cf2f3a;p=njs.git QuickJS: fix empty stack in errors created by the host An error created outside of a JS frame, from a fetch() completion callback for instance, records an empty stack. Up to QuickJS-NG 0.15.1 deleting the recorded "stack" property let the engine attach a real one where the error was thrown. Since 0.16.0 the stack is kept in the error internal data behind an accessor, the property to delete no longer exists and the empty stack became final. JS_NewError() is JS_NewObjectClass() plus the stack recording, and the first half alone is reachable through the public API. Creating the object that way records nothing, so the engine attaches the stack at the throw site. This works the same way for every supported engine, hence the feature test is gone. --- diff --git a/auto/quickjs b/auto/quickjs index 785ee783..80e79b2c 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -214,33 +214,6 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature - njs_feature="QuickJS JS_NewError() attaches stack" - njs_feature_run=value - njs_feature_name=NJS_HAVE_QUICKJS_NEW_ERROR_STACK - njs_feature_test="#include - - int main() { - int rc; - JSAtom atom; - JSValue err; - JSRuntime *rt; - JSContext *ctx; - - rt = JS_NewRuntime(); - ctx = JS_NewContext(rt); - err = JS_NewError(ctx); - atom = JS_NewAtom(ctx, \"stack\"); - rc = JS_HasProperty(ctx, err, atom); - printf(\"%d\", rc); - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, err); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return 0; - }" - - . auto/feature - njs_feature="QuickJS version" njs_feature_name=NJS_QUICKJS_VERSION njs_feature_run=value diff --git a/src/qjs.h b/src/qjs.h index cc45643f..32aa80d9 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -150,30 +150,26 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) } /* - * QuickJS-NG attaches the "stack" property to an Error object too early, - * which results in empty stack trace when called from C code. - * Removing it allows the stack to be attached later during unwinding. + * JS_NewError() records the stack of the caller, which is empty for an + * error created outside of a JS frame. Since QuickJS-NG 0.16.0 the + * recorded stack is also final, the engine never replaces it. Creating + * the object without letting the engine record anything allows the stack + * to be attached where the error is actually thrown. */ -static inline JSValue qjs_new_error2(JSContext *cx) +static inline JSValue qjs_new_error(JSContext *cx) { - JSAtom stack; - JSValue error; - - stack = JS_NewAtom(cx, "stack"); - if (stack == JS_ATOM_NULL) { - return JS_EXCEPTION; - } + JSValue error; + JSClassID class_id; error = JS_NewError(cx); if (JS_IsException(error)) { - JS_FreeAtom(cx, stack); return JS_EXCEPTION; } - JS_DeleteProperty(cx, error, stack, 0); - JS_FreeAtom(cx, stack); + class_id = JS_GetClassID(error); + JS_FreeValue(cx, error); - return error; + return JS_NewObjectClass(cx, class_id); } #ifdef NJS_HAVE_QUICKJS_IS_SAME_VALUE @@ -194,12 +190,6 @@ static inline JSValue qjs_new_error2(JSContext *cx) #define qjs_is_error(cx, a) JS_IsError(cx, a) #endif -#ifdef NJS_HAVE_QUICKJS_NEW_ERROR_STACK -#define qjs_new_error(cx) qjs_new_error2(cx) -#else -#define qjs_new_error(cx) JS_NewError(cx) -#endif - extern qjs_module_t *qjs_modules[]; #endif /* _QJS_H_INCLUDED_ */