]> git.kaiwu.me - njs.git/commitdiff
Attach JS stack trace for exceptions thrown by C code.
authorDmitry Volyntsev <xeioex@nginx.com>
Sat, 7 Feb 2026 00:29:20 +0000 (16:29 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Sat, 7 Feb 2026 01:46:14 +0000 (17:46 -0800)
This fixes #1019 issue on Github.

nginx/t/js_fetch_error_stack.t [new file with mode: 0644]
src/njs_async.c
src/njs_async.h
src/njs_vmcode.c

diff --git a/nginx/t/js_fetch_error_stack.t b/nginx/t/js_fetch_error_stack.t
new file mode 100644 (file)
index 0000000..8fc4900
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for http njs module, fetch method error stack traces.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http/)
+    ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    js_import test.js;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location /testAsync {
+            js_content test.testAsync;
+        }
+
+        location /testSync {
+            js_content test.testSync;
+        }
+
+        location /testAsyncThrow {
+            js_content test.testAsyncThrow;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+    async function testAsync(r) {
+        try {
+            await ngx.fetch('http://127.0.0.1:12345');
+
+        } catch(e) {
+            r.return(200, `message: ${e.message}\n stack: ${e.stack}\n`);
+        }
+    }
+
+    function testSync(r) {
+        try {
+            throw Error('oops');
+
+        } catch(e) {
+            r.return(200, `message: ${e.message}\n stack: ${e.stack}\n`);
+        }
+    }
+
+    async function testAsyncThrow(r) {
+        try {
+            throw Error('oops');
+
+        } catch(e) {
+            r.return(200, `message: ${e.message}\n stack: ${e.stack}\n`);
+        }
+    }
+
+    export default { testAsync, testSync, testAsyncThrow }
+EOF
+
+$t->try_run('no njs');
+
+$t->plan(6);
+
+###############################################################################
+
+like(http_get('/testSync'), qr/message: oops/s, 'sync stack exists');
+like(http_get('/testSync'), qr/at testSync \([^)]*test\.js:12/s,
+    'sync stack line number');
+
+like(http_get('/testAsync'), qr/message: connect failed/s,
+    'async stack exists');
+like(http_get('/testAsync'), qr/at testAsync \([^)]*test\.js:3/s,
+    'async stack line number');
+
+like(http_get('/testAsyncThrow'), qr/message: oops/s,
+    'async throw stack exists');
+like(http_get('/testAsyncThrow'), qr/at testAsyncThrow \([^)]*test\.js:21/s,
+    'async throw stack line number');
+
+###############################################################################
index c352e86e7adfdf99e3754663c486d7ff9596c17b..89f7f384a3a9d3c6e3da9adfe898e8e78e2bee9d 100644 (file)
@@ -82,6 +82,7 @@ njs_await_fulfilled(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     vm->active_frame = async_frame;
 
     if (exception) {
+        ctx->throw_flag = 1;
         njs_vm_throw(vm, value);
 
     } else {
@@ -128,27 +129,6 @@ njs_int_t
 njs_await_rejected(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused, njs_value_t *retval)
 {
-    njs_value_t      *value;
-    njs_async_ctx_t  *ctx;
-
-    ctx = vm->top_frame->function->context;
-
-    value = njs_arg(args, nargs, 1);
-
-    if (ctx->await->native.pc == ctx->pc) {
-        /* No catch block was set before await. */
-        (void) njs_function_call(vm, njs_function(&ctx->capability->reject),
-                                 &njs_value_undefined, value, 1, retval);
-
-        njs_async_context_free(vm, ctx);
-
-        return NJS_ERROR;
-    }
-
-    /* ctx->await->native.pc points to a catch block here. */
-
-    ctx->pc = ctx->await->native.pc;
-
     return njs_await_fulfilled(vm, args, nargs, 1, retval);
 }
 
index 3addb2d612939da401ef2f41394b1ae11bb4842a..ca7df2ac79ae2185306b0575560f54fe9bfb3ad0 100644 (file)
@@ -13,6 +13,7 @@ typedef struct {
     njs_frame_t               *await;
     uintptr_t                 index;
     u_char                    *pc;
+    int                       throw_flag;
 } njs_async_ctx_t;
 
 
index 6d991042c7408b399388fbf5694b362cfa319fb4..2dac79e6c13480fc0a3a5fba277e3ce976d62809 100644 (file)
@@ -229,6 +229,10 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
 
 #endif
 
+    if (async_ctx != NULL && ((njs_async_ctx_t *) async_ctx)->throw_flag) {
+        goto error;
+    }
+
     vmcode = (njs_vmcode_generic_t *) pc;
 
 NEXT_LBL;
@@ -2722,7 +2726,6 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await,
 {
     size_t              size;
     njs_int_t           ret;
-    njs_frame_t         *frame;
     njs_value_t         ctor, val, on_fulfilled, on_rejected, *value, retval;
     njs_function_t      *fulfilled, *rejected;
     njs_native_frame_t  *active;
@@ -2773,15 +2776,7 @@ njs_vmcode_await(njs_vm_t *vm, njs_vmcode_await_t *await,
 
     ctx->pc = (u_char *) await + sizeof(njs_vmcode_await_t);
     ctx->index = await->retval;
-
-    frame = (njs_frame_t *) active;
-
-    if (frame->exception.catch != NULL) {
-        ctx->await->native.pc = frame->exception.catch;
-
-    } else {
-        ctx->await->native.pc = ctx->pc;
-    }
+    ctx->throw_flag = 0;
 
     fulfilled->context = ctx;
     fulfilled->args_count = 1;