From: Dmitry Volyntsev Date: Wed, 3 Jun 2026 02:07:08 +0000 (-0700) Subject: Stream: fixed exception classes X-Git-Tag: 1.0.0~25 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/NGINX-js-1660x332.png%20%22NGINX%20JavaScript%20Banner%22?a=commitdiff_plain;h=bce77d44c0959d1a63934f04e4ae2ed86cda6819;p=njs.git Stream: fixed exception classes Report stream session API misuse as TypeError, including wrong receiver, unknown or duplicate event handlers, wrong handler phase, and invalid variable access. Report status bounds violations as RangeError and keep stream output pipeline failures as InternalError. Also fix the async send error message spacing. --- diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 308ddfd1..2332b42e 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -1446,7 +1446,8 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) } if (i == n) { - njs_vm_error(ctx->engine->u.njs.vm, "unknown event \"%V\"", event); + njs_vm_type_error(ctx->engine->u.njs.vm, "unknown event \"%V\"", + event); return NULL; } @@ -1455,8 +1456,8 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) for (n = 0; n < NGX_JS_EVENT_MAX; n++) { type = ctx->events[n].data_type; if (type != NGX_JS_UNSET && type != events[i].data_type) { - njs_vm_error(ctx->engine->u.njs.vm, "mixing string and buffer" - " events is not allowed"); + njs_vm_type_error(ctx->engine->u.njs.vm, "mixing string and buffer" + " events is not allowed"); return NULL; } } @@ -1498,7 +1499,7 @@ ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } @@ -1517,7 +1518,7 @@ ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) { - njs_vm_error(vm, "code is out of range"); + njs_vm_range_error(vm, "code is out of range"); return NJS_ERROR; } } @@ -1526,7 +1527,7 @@ ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (ctx->filter) { - njs_vm_error(vm, "should not be called while filtering"); + njs_vm_type_error(vm, "should not be called while filtering"); return NJS_ERROR; } @@ -1555,18 +1556,18 @@ ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) { - njs_vm_error(vm, "failed to convert event arg"); + njs_vm_type_error(vm, "failed to convert event arg"); return NJS_ERROR; } callback = njs_arg(args, nargs, 2); if (!njs_value_is_function(callback)) { - njs_vm_error(vm, "callback is not a function"); + njs_vm_type_error(vm, "callback is not a function"); return NJS_ERROR; } @@ -1576,7 +1577,7 @@ ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } if (njs_value_is_function(njs_value_arg(&event->function))) { - njs_vm_error(vm, "event handler \"%V\" is already set", &name); + njs_vm_type_error(vm, "event handler \"%V\" is already set", &name); return NJS_ERROR; } @@ -1599,12 +1600,12 @@ ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } if (ngx_js_string(vm, njs_arg(args, nargs, 1), &name) == NJS_ERROR) { - njs_vm_error(vm, "failed to convert event arg"); + njs_vm_type_error(vm, "failed to convert event arg"); return NJS_ERROR; } @@ -1643,7 +1644,7 @@ ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } @@ -1652,12 +1653,12 @@ ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->filter) { - njs_vm_error(vm, "cannot send buffer in this handler"); + njs_vm_type_error(vm, "cannot send buffer in this handler"); return NJS_ERROR; } if (ngx_js_string(vm, njs_arg(args, nargs, 1), &buffer) != NGX_OK) { - njs_vm_error(vm, "failed to get buffer arg"); + njs_vm_type_error(vm, "failed to get buffer arg"); return NJS_ERROR; } @@ -1702,7 +1703,7 @@ ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, cl = ngx_chain_get_free_buf(c->pool, &ctx->free); if (cl == NULL) { - njs_vm_error(vm, "memory error"); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -1727,7 +1728,7 @@ ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } else { if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) { - njs_vm_error(vm, "ngx_stream_js_next_filter() failed"); + njs_vm_internal_error(vm, "ngx_stream_js_next_filter() failed"); return NJS_ERROR; } } @@ -1738,8 +1739,8 @@ ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, exception: - njs_vm_error(vm, "\"from_upstream\" flag is expected when" - "called asynchronously"); + njs_vm_type_error(vm, "\"from_upstream\" flag is expected when " + "called asynchronously"); return NJS_ERROR; } @@ -1755,7 +1756,7 @@ ngx_stream_js_ext_set_return_value(njs_vm_t *vm, njs_value_t *args, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } @@ -1796,7 +1797,7 @@ ngx_stream_js_ext_js_var_names(njs_vm_t *vm, njs_value_t *args, s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); if (s == NULL) { - njs_vm_error(vm, "\"this\" is not an external"); + njs_vm_type_error(vm, "\"this\" is not an external"); return NJS_ERROR; } @@ -1877,7 +1878,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, } else { name.data = ngx_pnalloc(s->connection->pool, val.length); if (name.data == NULL) { - njs_vm_error(vm, "internal error"); + njs_vm_memory_error(vm); return NJS_ERROR; } } @@ -1904,7 +1905,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, } else { name.data = ngx_pnalloc(s->connection->pool, val.length); if (name.data == NULL) { - njs_vm_error(vm, "internal error"); + njs_vm_memory_error(vm); return NJS_ERROR; } } @@ -1914,7 +1915,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, v = ngx_hash_find(&cmcf->variables_hash, key, name.data, val.length); if (v == NULL) { - njs_vm_error(vm, "variable not found"); + njs_vm_type_error(vm, "variable not found"); return NJS_ERROR; } @@ -1926,6 +1927,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, vv = ngx_pcalloc(s->connection->pool, sizeof(ngx_stream_variable_value_t)); if (vv == NULL) { + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -1940,7 +1942,7 @@ ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop, } if (!(v->flags & NGX_STREAM_VAR_INDEXED)) { - njs_vm_error(vm, "variable is not writable"); + njs_vm_type_error(vm, "variable is not writable"); return NJS_ERROR; } @@ -2130,7 +2132,7 @@ ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } status = (ngx_int_t) magic; @@ -2146,15 +2148,14 @@ ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, } if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) { - return JS_ThrowInternalError(cx, "code is out of range"); + return JS_ThrowRangeError(cx, "code is out of range"); } } ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (ctx->filter) { - return JS_ThrowInternalError(cx, "should not be called while " - "filtering"); + return JS_ThrowTypeError(cx, "should not be called while filtering"); } ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, @@ -2178,7 +2179,7 @@ ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } for (n = 0; n < argc; n++) { @@ -2251,8 +2252,8 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, JSValue name) } if (i == n) { - (void) JS_ThrowInternalError(cx, "unknown event \"%.*s\"", - (int) event.len, event.data); + (void) JS_ThrowTypeError(cx, "unknown event \"%.*s\"", + (int) event.len, event.data); JS_FreeCString(cx, (char *) event.data); return NULL; } @@ -2264,8 +2265,8 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, JSValue name) for (n = 0; n < NGX_JS_EVENT_MAX; n++) { type = ctx->events[n].data_type; if (type != NGX_JS_UNSET && type != events[i].data_type) { - (void) JS_ThrowInternalError(cx, "mixing string and buffer" - " events is not allowed"); + (void) JS_ThrowTypeError(cx, "mixing string and buffer" + " events is not allowed"); return NULL; } } @@ -2284,7 +2285,7 @@ ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_SESSION); if (ses == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module); @@ -2295,8 +2296,8 @@ ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, } if (JS_IsFunction(cx, ngx_qjs_arg(ctx->events[e->id].function))) { - return JS_ThrowInternalError(cx, "event handler \"%s\" is already set", - e->name.data); + return JS_ThrowTypeError(cx, "event handler \"%s\" is already set", + e->name.data); } if (!JS_IsFunction(cx, argv[1])) { @@ -2322,7 +2323,7 @@ ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); @@ -2348,7 +2349,7 @@ ngx_stream_qjs_ext_periodic_variables(JSContext *cx, ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_PERIODIC); if (ses == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a periodic object"); + return JS_ThrowTypeError(cx, "\"this\" is not a periodic object"); } obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS); @@ -2371,7 +2372,7 @@ ngx_stream_qjs_ext_remote_address(JSContext *cx, JSValueConst this_val) s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } c = s->connection; @@ -2397,7 +2398,7 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } c = s->connection; @@ -2405,7 +2406,7 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->filter) { - return JS_ThrowInternalError(cx, "cannot send buffer in this handler"); + return JS_ThrowTypeError(cx, "cannot send buffer in this handler"); } /* @@ -2455,9 +2456,9 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc, } if (from_upstream == NGX_JS_BOOL_UNSET && ctx->buf == NULL) { - return JS_ThrowInternalError(cx, "from_upstream flag is " - "expected when called " - "asynchronously"); + return JS_ThrowTypeError(cx, "from_upstream flag is " + "expected when called " + "asynchronously"); } } } @@ -2576,7 +2577,7 @@ out_of_memory: JS_FreeValue(cx, buf); - return JS_ThrowInternalError(cx, "memory error"); + return JS_ThrowOutOfMemory(cx); } @@ -2589,7 +2590,7 @@ ngx_stream_qjs_ext_set_return_value(JSContext *cx, JSValueConst this_val, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); @@ -2609,7 +2610,7 @@ ngx_stream_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type) s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS); @@ -2639,7 +2640,7 @@ ngx_stream_qjs_ext_js_var_names(JSContext *cx, JSValueConst this_val, int argc, s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } prefix = NULL; @@ -2707,7 +2708,7 @@ ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val, int offset) s = ngx_stream_qjs_session(this_val); if (s == NULL) { - return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return JS_ThrowTypeError(cx, "\"this\" is not a session object"); } field = (ngx_uint_t *) ((u_char *) s + offset); @@ -2744,7 +2745,7 @@ ngx_stream_qjs_variables_own_property(JSContext *cx, s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1); if (s == NULL) { - (void) JS_ThrowInternalError(cx, "\"this\" is not a session object"); + (void) JS_ThrowTypeError(cx, "\"this\" is not a session object"); return -1; } @@ -2805,7 +2806,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1); if (s == NULL) { - (void) JS_ThrowInternalError(cx, "\"this\" is not a session object"); + (void) JS_ThrowTypeError(cx, "\"this\" is not a session object"); return -1; } @@ -2836,7 +2837,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, JS_FreeCString(cx, (char *) name.data); if (v == NULL) { - (void) JS_ThrowInternalError(cx, "variable not found"); + (void) JS_ThrowTypeError(cx, "variable not found"); return -1; } diff --git a/nginx/t/stream_js.t b/nginx/t/stream_js.t index a66609fc..8e4b9726 100644 --- a/nginx/t/stream_js.t +++ b/nginx/t/stream_js.t @@ -70,6 +70,11 @@ stream { js_set $js_async test.asyncf; js_set $js_async_direct test.asyncf_direct; js_set $js_buffer test.buffer; + js_set $js_done_range test.done_range; + js_set $js_event_unknown test.event_unknown; + js_set $js_event_dup test.event_dup; + js_set $js_send_state test.send_state; + js_set $js_var_not_found test.var_not_found; js_import test.js; @@ -202,6 +207,31 @@ stream { listen 127.0.0.1:8101; return $js_buffer; } + + server { + listen 127.0.0.1:8103; + return $js_done_range; + } + + server { + listen 127.0.0.1:8104; + return $js_event_unknown; + } + + server { + listen 127.0.0.1:8105; + return $js_event_dup; + } + + server { + listen 127.0.0.1:8106; + return $js_send_state; + } + + server { + listen 127.0.0.1:8107; + return $js_var_not_found; + } } EOF @@ -397,17 +427,51 @@ $t->write_file('test.js', < s.done(-999)); + } + + function event_unknown(s) { + return error_name(() => s.on('unknown', () => {})); + } + + function event_dup(s) { + return error_name(() => { + s.on('upload', () => {}); + s.on('upload', () => {}); + }); + } + + function send_state(s) { + return error_name(() => s.send('x')); + } + + function var_not_found(s) { + return error_name(() => { s.variables.unknown = 1 }); + } + export default {njs:test_njs, addr, variable, sess_unk, log, access_step, preread_step, filter_step, access_undecided, access_allow, access_deny, preread_async, preread_data, preread_req_line, req_line, filter_empty, filter_header_inject, filter_search, access_except, preread_except, filter_except, asyncf, - asyncf_direct, buffer}; + asyncf_direct, buffer, done_range, event_unknown, + event_dup, send_state, var_not_found}; EOF $t->run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no stream njs available')->plan(25); +$t->try_run('no stream njs available')->plan(30); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -453,6 +517,19 @@ like(stream('127.0.0.1:' . port(8101))->read(), qr/\xaa\xbb\xcc\xdd/, } +is(stream('127.0.0.1:' . port(8103))->read(), + 'RangeError:code is out of range', 's.done code range'); +like(stream('127.0.0.1:' . port(8104))->read(), + qr/^TypeError:unknown event "unknown"/, 's.on unknown event'); +like(stream('127.0.0.1:' . port(8105))->read(), + qr/^TypeError:event handler "upload" is already set/, + 's.on duplicate handler'); +is(stream('127.0.0.1:' . port(8106))->read(), + 'TypeError:cannot send buffer in this handler', + 's.send wrong handler'); +is(stream('127.0.0.1:' . port(8107))->read(), + 'TypeError:variable not found', 's.variables unknown set'); + $t->stop(); ok(index($t->read_file('error.log'), 'SEE-THIS') > 0, 'stream js log'); diff --git a/nginx/t/stream_js_buffer.t b/nginx/t/stream_js_buffer.t index be6569f2..30e99a7c 100644 --- a/nginx/t/stream_js_buffer.t +++ b/nginx/t/stream_js_buffer.t @@ -123,7 +123,7 @@ $t->write_file('test.js', < {}); s.on('downstream', () => {}); } catch (e) { - throw new Error(`cb_mismatch:\${e.message}`) + throw new Error(`cb_mismatch:\${e.constructor.name}:\${e.message}`) } } @@ -132,7 +132,7 @@ $t->write_file('test.js', < {}); s.on('download', () => {}); } catch (e) { - throw new Error(`cb_mismatch2:\${e.message}`) + throw new Error(`cb_mismatch2:\${e.constructor.name}:\${e.message}`) } } @@ -192,10 +192,12 @@ is(stream('127.0.0.1:' . port(8086))->io('x', length => 6), 'ellllo', $t->stop(); -ok(index($t->read_file('error.log'), 'cb_mismatch:mixing string and buffer') - > 0, 'cb mismatch'); -ok(index($t->read_file('error.log'), 'cb_mismatch2:mixing string and buffer') - > 0, 'cb mismatch'); +ok(index($t->read_file('error.log'), + 'cb_mismatch:TypeError:mixing string and buffer') > 0, + 'cb mismatch'); +ok(index($t->read_file('error.log'), + 'cb_mismatch2:TypeError:mixing string and buffer') > 0, + 'cb mismatch'); ###############################################################################