]> git.kaiwu.me - njs.git/commitdiff
QuickJS: fix empty stack in errors created by the host
authorDmitry Volyntsev <xeioex@nginx.com>
Mon, 17 Aug 2026 16:33:34 +0000 (09:33 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 20 Aug 2026 17:31:10 +0000 (10:31 -0700)
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.

auto/quickjs
src/qjs.h

index 785ee7832e3871e297630bb3c8991b4ad4f4123d..80e79b2c66809ee944f90eeba5e7791b64927ac3 100644 (file)
@@ -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 <quickjs_compat.h>
-
-                          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
index cc45643fd4c551c743f5572fc949abab4203e8dc..32aa80d992be67e472e92d7083080332d38080b8 100644 (file)
--- 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_ */