From: Dmitry Volyntsev Date: Wed, 4 Mar 2026 04:40:19 +0000 (-0800) Subject: Preserve "this" for grouped optional calls. X-Git-Tag: 0.9.7~19 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=c015c830940b1b935bcacb7292043aa41c0e7066;p=njs.git Preserve "this" for grouped optional calls. Previously, grouped optional calls like (o?.m)() resolved the callee through the optional chain but dispatched via plain FUNCTION_CALL, losing the original receiver. The fix stores the receiver on the call node so the upcoming call-argument reorder can emit METHOD_FRAME with an explicit "this". Call-expression setup and optional-chain preserve lookup are routed through named helpers with generator-side validation. --- diff --git a/src/njs_generator.c b/src/njs_generator.c index 58656342..12ccc527 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -117,6 +117,41 @@ njs_generate_is_property_lvalue(njs_parser_node_t *node) } +njs_inline njs_bool_t +njs_generate_is_property_call_source(njs_parser_node_t *node) +{ + return node != NULL && node->token_type == NJS_TOKEN_PROPERTY_REF; +} + + +njs_inline njs_parser_node_t * +njs_generate_optional_method_call_preserve(njs_parser_node_t *node) +{ + return node->u.object; +} + + +njs_inline njs_parser_node_t * +njs_generate_optional_method_call_property(njs_parser_node_t *node) +{ + return node->left; +} + + +njs_inline njs_parser_node_t * +njs_generate_optional_chain_preserve(njs_parser_node_t *node) +{ + return node->u.object; +} + + +njs_inline njs_parser_node_t * +njs_generate_function_call_this(njs_parser_node_t *node) +{ + return node->u.object; +} + + static u_char *njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size); static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator, @@ -4147,16 +4182,35 @@ njs_generate_test_jump_expression_end(njs_vm_t *vm, njs_generator_t *generator, static njs_parser_node_t * -njs_generate_optional_method_call(njs_parser_node_t *node) +njs_generate_optional_method_call(njs_vm_t *vm, njs_parser_node_t *node) { - if (node != NULL - && node->token_type == NJS_TOKEN_METHOD_CALL - && node->u.object != NULL) + njs_parser_node_t *preserve; + + if (node == NULL + || node->token_type != NJS_TOKEN_METHOD_CALL + || node->u.object == NULL) { - return node; + return NULL; } - return NULL; + if (!njs_generate_is_property_call_source(node->left)) { + njs_internal_error(vm, "unexpected optional method call source"); + return NULL; + } + + preserve = njs_generate_optional_method_call_preserve(node); + + if (preserve->left == NULL || preserve->right == NULL) { + njs_internal_error(vm, "unexpected optional method call state"); + return NULL; + } + + if (!njs_generate_is_property_lvalue(preserve)) { + njs_internal_error(vm, "unexpected optional method call preserve"); + return NULL; + } + + return node; } @@ -4169,9 +4223,9 @@ njs_generate_optional_chain(njs_vm_t *vm, njs_generator_t *generator, jump_offset = 0; - call = njs_generate_optional_method_call(node->right); + call = njs_generate_optional_method_call(vm, node->right); if (call != NULL) { - preserve = call->u.object->left; + preserve = njs_generate_optional_method_call_preserve(call)->left; if (njs_generate_is_property_lvalue(preserve)) { preserve->hoist = 1; @@ -4209,14 +4263,16 @@ njs_generate_optional_chain_after(njs_vm_t *vm, njs_generator_t *generator, test_jump->retval = node->index; - call = njs_generate_optional_method_call(node->right); + call = njs_generate_optional_method_call(vm, node->right); if (call != NULL) { - prop = call->left; - prop->left->index = call->u.object->left->index; - prop->right->index = call->u.object->right->index; + prop = njs_generate_optional_method_call_property(call); + prop->left->index = njs_generate_optional_method_call_preserve(call) + ->left->index; + prop->right->index = njs_generate_optional_method_call_preserve(call) + ->right->index; - } else if (node->u.object != NULL) { - node->u.object->index = node->left->index; + } else if (njs_generate_optional_chain_preserve(node) != NULL) { + njs_generate_optional_chain_preserve(node)->index = node->left->index; } njs_generator_next(generator, njs_generate, node->right); @@ -4247,10 +4303,9 @@ njs_generate_optional_chain_end(njs_vm_t *vm, njs_generator_t *generator, njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t, *jump_offset); - call = njs_generate_optional_method_call(node->right); + call = njs_generate_optional_method_call(vm, node->right); if (call != NULL) { - preserve = call->u.object->left; - + preserve = njs_generate_optional_method_call_preserve(call)->left; if (!njs_generate_is_property_lvalue(preserve)) { preserve = NULL; } @@ -5017,12 +5072,26 @@ static njs_int_t njs_generate_function_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; - njs_variable_t *var; + njs_int_t ret; + njs_variable_t *var; + njs_parser_node_t *this_object; var = NULL; + this_object = njs_generate_function_call_this(node); if (node->left != NULL) { + if (njs_generate_is_property_call_source(node->left)) { + njs_internal_error(vm, "unexpected function call source"); + return NJS_ERROR; + } + + if (this_object != NULL + && node->left->token_type != NJS_TOKEN_OPTIONAL_CHAIN) + { + njs_internal_error(vm, "unexpected function call this"); + return NJS_ERROR; + } + /* Generate function code in function expression. */ njs_generator_next(generator, njs_generate, node->left); @@ -5089,13 +5158,22 @@ static njs_int_t njs_generate_function_call_end(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; + njs_int_t ret; + njs_parser_node_t *this_object; ret = njs_generate_call(vm, generator, node); if (njs_fast_path(ret != NJS_OK)) { return ret; } + this_object = njs_generate_function_call_this(node); + if (this_object != NULL && this_object->temporary) { + ret = njs_generate_index_release(vm, generator, this_object->index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + return njs_generator_stack_pop(vm, generator, generator->context); } @@ -5107,12 +5185,17 @@ njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator, njs_int_t ret; njs_parser_node_t *prop; - if (njs_generate_optional_method_call(node) != NULL) { + if (njs_generate_optional_method_call(vm, node) != NULL) { return njs_generate_method_call_arguments(vm, generator, node); } prop = node->left; + if (!njs_generate_is_property_call_source(prop)) { + njs_internal_error(vm, "unexpected method call source"); + return NJS_ERROR; + } + /* Object. */ njs_generator_next(generator, njs_generate, prop->left); @@ -5143,6 +5226,11 @@ njs_generate_method_call_arguments(njs_vm_t *vm, njs_generator_t *generator, prop = node->left; + if (!njs_generate_is_property_call_source(prop)) { + njs_internal_error(vm, "unexpected method call source"); + return NJS_ERROR; + } + njs_generate_code(generator, njs_vmcode_method_frame_t, method, NJS_VMCODE_METHOD_FRAME, prop); method_offset = njs_code_offset(generator, method); diff --git a/src/njs_parser.c b/src/njs_parser.c index b17fe390..5a56f0cd 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -98,8 +98,22 @@ static njs_int_t njs_parser_call_arguments(njs_parser_t *parser, static njs_int_t njs_parser_right_link_pop(njs_parser_t *parser); static njs_parser_node_t *njs_parser_property_ref(njs_parser_node_t *node); static njs_parser_node_t *njs_parser_lvalue_ref(njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_call_receiver(njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_optional_chain_preserve( + njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line); +static njs_parser_node_t *njs_parser_optional_chain_source( + njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_optional_chain_receiver( + njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_optional_chain_call_this( + njs_parser_node_t *node); +static njs_parser_node_t *njs_parser_optional_chain_target_property( + njs_parser_node_t *node); static njs_parser_node_t *njs_parser_optional_chain_method_call( njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line); +static njs_parser_node_t *njs_parser_optional_chain_call( + njs_parser_t *parser, njs_parser_node_t *node, + njs_parser_node_t *fallback, uint32_t token_line); static njs_int_t njs_parser_call_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_call_expression_args(njs_parser_t *parser, @@ -2674,31 +2688,67 @@ njs_parser_lvalue_ref(njs_parser_node_t *node) static njs_parser_node_t * -njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, - uint8_t ctor) +njs_parser_call_receiver(njs_parser_node_t *node) { - njs_parser_node_t *func; + return njs_parser_property_ref(node); +} - switch (node->token_type) { - case NJS_TOKEN_NAME: - func = node; - func->token_type = NJS_TOKEN_FUNCTION_CALL; - break; +static njs_parser_node_t * +njs_parser_optional_chain_preserve(njs_parser_t *parser, + njs_parser_node_t *node, uint32_t token_line) +{ + njs_parser_node_t *ref; - case NJS_TOKEN_PROPERTY: - case NJS_TOKEN_PROPERTY_REF: - node = njs_parser_property_ref(node); - if (node == NULL) { - return NULL; - } + ref = njs_parser_node_new(parser, NJS_TOKEN_OBJECT_VALUE); + if (ref == NULL) { + return NULL; + } + + ref->token_line = token_line; + ref->u.object = node; + + return ref; +} + + +static njs_parser_node_t * +njs_parser_optional_chain_source(njs_parser_node_t *node) +{ + if (node != NULL && node->token_type == NJS_TOKEN_OBJECT_VALUE) { + return node->u.object; + } + return NULL; +} + + +static njs_parser_node_t * +njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, + uint8_t ctor) +{ + njs_parser_node_t *func, *receiver, *this_object; + + receiver = njs_parser_call_receiver(node); + if (receiver != NULL) { func = njs_parser_node_new(parser, NJS_TOKEN_METHOD_CALL); if (func == NULL) { return NULL; } - func->left = node; + func->left = receiver; + func->ctor = ctor; + + return func; + } + + this_object = njs_parser_optional_chain_call_this(node); + + switch (node->token_type) { + case NJS_TOKEN_NAME: + func = node; + func->token_type = NJS_TOKEN_FUNCTION_CALL; + break; default: @@ -2719,6 +2769,7 @@ njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, } func->ctor = ctor; + func->u.object = this_object; return func; } @@ -2750,22 +2801,16 @@ njs_parser_right_link_pop(njs_parser_t *parser) static njs_parser_node_t * -njs_parser_optional_chain_property(njs_parser_node_t *node) +njs_parser_optional_chain_receiver(njs_parser_node_t *node) { - if (node == NULL) { - return NULL; - } - - if (node->token_type == NJS_TOKEN_PROPERTY - || node->token_type == NJS_TOKEN_PROPERTY_REF) - { + if (njs_parser_call_receiver(node) != NULL) { return node; } - if (node->token_type == NJS_TOKEN_OPTIONAL_CHAIN + if (node != NULL + && node->token_type == NJS_TOKEN_OPTIONAL_CHAIN && node->right != NULL - && (node->right->token_type == NJS_TOKEN_PROPERTY - || node->right->token_type == NJS_TOKEN_PROPERTY_REF)) + && njs_parser_call_receiver(node->right) != NULL) { return node->right; } @@ -2774,13 +2819,38 @@ njs_parser_optional_chain_property(njs_parser_node_t *node) } +static njs_parser_node_t * +njs_parser_optional_chain_call_this(njs_parser_node_t *node) +{ + njs_parser_node_t *receiver; + + receiver = njs_parser_optional_chain_receiver(node); + if (receiver != NULL) { + return receiver->left; + } + + return NULL; +} + + +static njs_parser_node_t * +njs_parser_optional_chain_target_property(njs_parser_node_t *node) +{ + if (node == NULL || node->token_type != NJS_TOKEN_OPTIONAL_CHAIN) { + return NULL; + } + + return njs_parser_property_ref(node->right); +} + + static njs_parser_node_t * njs_parser_optional_chain_method_call(njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line) { njs_parser_node_t *func, *prop, *src; - src = njs_parser_optional_chain_property(node); + src = njs_parser_optional_chain_receiver(node); if (src == NULL) { return NULL; } @@ -2795,18 +2865,32 @@ njs_parser_optional_chain_method_call(njs_parser_t *parser, prop->left = src->left; prop->right = src->right; - func = njs_parser_node_new(parser, NJS_TOKEN_METHOD_CALL); + func = njs_parser_create_call(parser, prop, 0); if (func == NULL) { return NULL; } - func->left = prop; func->u.object = src; return func; } +static njs_parser_node_t * +njs_parser_optional_chain_call(njs_parser_t *parser, njs_parser_node_t *node, + njs_parser_node_t *fallback, uint32_t token_line) +{ + njs_parser_node_t *func; + + func = njs_parser_optional_chain_method_call(parser, node, token_line); + if (func != NULL) { + return func; + } + + return njs_parser_create_call(parser, fallback, 0); +} + + static njs_int_t njs_parser_call_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -3090,13 +3174,12 @@ njs_parser_optional_expression_after(njs_parser_t *parser, opt->left = parser->node; opt->left->dest = opt; - ref = njs_parser_node_new(parser, NJS_TOKEN_OBJECT_VALUE); + ref = njs_parser_optional_chain_preserve(parser, parser->node, + token->line); if (ref == NULL) { return NJS_ERROR; } - ref->token_line = token->line; - ref->u.object = parser->node; opt->u.object = ref; parser->node = ref; @@ -3149,14 +3232,12 @@ njs_parser_optional_chain(njs_parser_t *parser, njs_lexer_token_t *token, switch (token->type) { case NJS_TOKEN_OPEN_PARENTHESIS: - func = njs_parser_optional_chain_method_call(parser, - parser->node->u.object, - token->line); + func = njs_parser_optional_chain_call(parser, + njs_parser_optional_chain_source( + parser->node), + parser->node, token->line); if (func == NULL) { - func = njs_parser_create_call(parser, parser->node, 0); - if (func == NULL) { - return NJS_ERROR; - } + return NJS_ERROR; } ret = njs_parser_call_arguments(parser, token, current, func, @@ -3720,7 +3801,7 @@ njs_parser_unary_expression_next(njs_parser_t *parser, return njs_parser_stack_pop(parser); case NJS_TOKEN_OPTIONAL_CHAIN: - prop = njs_parser_optional_chain_property(node); + prop = njs_parser_optional_chain_target_property(node); if (prop != NULL) { prop->token_type = NJS_TOKEN_PROPERTY_DELETE; prop->u.operation = NJS_VMCODE_PROPERTY_DELETE;