]> git.kaiwu.me - njs.git/commitdiff
Modules: fixed stack attach for native fetch exceptions for qjs.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 4 Feb 2026 02:17:16 +0000 (18:17 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Sat, 7 Feb 2026 01:46:14 +0000 (17:46 -0800)
JS_ThrowInternalError() captures backtrace outside of JS call, resulting
in empty "stack" string.  Later, while unwinding, is_backtrace_needed()
returns false because "stack" property exists.

JS_NewError() does not try to attach stack, as a result a stack is
attached later during unwinding.

auto/quickjs
external/qjs_fs_module.c
nginx/ngx_qjs_fetch.c
src/qjs.h

index f14617d5f9d8cdd9dd66e0d195dc3e51cbaaf632..630f9870083d2914a365d39aae935e53bb568c9c 100644 (file)
@@ -187,6 +187,33 @@ 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 af929db34f5d9298cd57909a1875f552248ec7c0..89c5281c14068dcbe18437931234914825e9a443 100644 (file)
@@ -2805,7 +2805,7 @@ qjs_fs_error(JSContext *cx, const char *syscall, const char *description,
 {
     JSValue  value;
 
-    value = JS_NewError(cx);
+    value = qjs_new_error(cx);
     if (JS_IsException(value)) {
         return JS_EXCEPTION;
     }
index 731fba44b6d76c3cfb7c68d089774b89f98accd2..8e010bec824e2dd7d2e943c3b476b3aa0ba3bca1 100644 (file)
@@ -1195,13 +1195,30 @@ fail:
 static void
 ngx_qjs_fetch_error(ngx_js_http_t *http, const char *err)
 {
+    JSValue           reason;
     ngx_qjs_fetch_t  *fetch;
 
     fetch = (ngx_qjs_fetch_t *) http;
 
-    JS_ThrowInternalError(fetch->cx, "%s", err);
+    fetch->response_value = qjs_new_error(fetch->cx);
+    if (JS_IsException(fetch->response_value)) {
+        fetch->response_value = JS_UNDEFINED;
+        goto done;
+    }
+
+    reason = JS_NewString(fetch->cx, err);
+    if (JS_IsException(reason)) {
+        goto done;
+    }
+
+    if (JS_SetPropertyStr(fetch->cx, fetch->response_value, "message",
+                          reason) < 0)
+    {
+        JS_FreeValue(fetch->cx, reason);
+        goto done;
+    }
 
-    fetch->response_value = JS_GetException(fetch->cx);
+done:
 
     ngx_qjs_fetch_done(fetch, fetch->response_value, NGX_ERROR);
 }
index 240e150ba79d471005ed8b0a42382af03a8af343..0d911a86b07def69174b6a82ba880389707da3f3 100644 (file)
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -138,6 +138,33 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v)
            || JS_VALUE_GET_TAG(v) == JS_TAG_UNDEFINED;
 }
 
+/*
+ * 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.
+ */
+static inline JSValue qjs_new_error2(JSContext *cx)
+{
+    JSAtom   stack;
+    JSValue  error;
+
+    stack = JS_NewAtom(cx, "stack");
+    if (stack == JS_ATOM_NULL) {
+        return JS_EXCEPTION;
+    }
+
+    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);
+
+    return error;
+}
+
 #ifdef NJS_HAVE_QUICKJS_IS_SAME_VALUE
 #define qjs_is_same_value(cx, a, b) JS_IsSameValue(cx, a, b)
 #else
@@ -156,6 +183,12 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v)
 #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_ */