]> git.kaiwu.me - njs.git/commitdiff
Preserve "this" for grouped optional calls.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 4 Mar 2026 04:40:19 +0000 (20:40 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 12 Mar 2026 22:26:01 +0000 (15:26 -0700)
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.

src/njs_generator.c
src/njs_parser.c

index 5865634232365317817421969c55669c7f4162c0..12ccc527499718bd5dcbf9ce02e4050cfe3350da 100644 (file)
@@ -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);
index b17fe3905e381af29e1376bd7fe5a783319a5f7d..5a56f0cdba73db2d9b8c20d4eb33b04f4eb7f313 100644 (file)
@@ -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;