]> git.kaiwu.me - njs.git/commitdiff
Fixed call argument evaluation.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 4 Mar 2026 07:22:54 +0000 (23:22 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 12 Mar 2026 22:26:01 +0000 (15:26 -0700)
Previously, call lowering created FUNCTION_FRAME and METHOD_FRAME before
argument evaluation.  This made call ordering observably wrong: for
non-callable callees, the error was thrown before arguments with side
effects were evaluated, violating the ECMAScript specification.  It also
prevented await expressions in call arguments, which were rejected at
parse time because suspending inside a half-created frame was not
supported.

The fix evaluates arguments first, then emits the frame, PUT_ARG, and
FUNCTION_CALL.  Callee and receiver values are captured into temporaries
before argument evaluation to guard against argument side effects.
Method properties are resolved via PROPERTY_GET before arguments.

METHOD_FRAME is redefined from a composite opcode (property lookup +
callability check + frame creation) to a pure frame-creation opcode
that takes an already-resolved function and explicit "this" value.
The parser always wraps call expressions in a NJS_TOKEN_FUNCTION_CALL
node, removing the NJS_TOKEN_NAME special case.

src/njs_disassembler.c
src/njs_generator.c
src/njs_parser.c
src/njs_vmcode.c
src/njs_vmcode.h
src/test/njs_unit_test.c
test/shell_test_njs.exp

index b1e7aa50b4e4653cd07c2206aff1dd0e6be8e864..e7de5ecd7f4b9175d9c6069cdfdfc637993224b6 100644 (file)
@@ -53,6 +53,8 @@ static njs_code_name_t  code_names[] = {
 
     { NJS_VMCODE_FUNCTION_CALL, sizeof(njs_vmcode_function_call_t),
           njs_str("FUNCTION CALL   ") },
+    { NJS_VMCODE_METHOD_FRAME, sizeof(njs_vmcode_method_frame_t),
+          njs_str("METHOD FRAME    ") },
     { NJS_VMCODE_RETURN, sizeof(njs_vmcode_return_t),
           njs_str("RETURN          ") },
     { NJS_VMCODE_STOP, sizeof(njs_vmcode_stop_t),
@@ -354,8 +356,8 @@ njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines)
             method = (njs_vmcode_method_frame_t *) p;
 
             njs_printf("%5uD | %05uz METHOD FRAME      %04Xz %04Xz %uz%s\n",
-                       line, p - start, (size_t) method->object,
-                       (size_t) method->method, method->nargs,
+                       line, p - start, (size_t) method->function,
+                       (size_t) method->this_object, method->nargs,
                        method->ctor ? " CTOR" : "");
 
             p += sizeof(njs_vmcode_method_frame_t);
index 8041abe6bcc7e2194ea7daef8b9a6d650382aa3e..89d08a11c706133efeeb7ae038027bc60b8888ef 100644 (file)
@@ -454,18 +454,29 @@ static njs_int_t njs_generate_function_call(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_function_call_arguments(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_function_call_frame(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_function_call_end(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_method_call(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_method_call_arguments(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_method_call_prepare(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_method_call_frame(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_call_argument_expressions(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_call_argument_expressions_after(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_uint_t njs_generate_call_nargs(njs_parser_node_t *node);
+static njs_int_t njs_generate_capture_stable_value(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_method_call_end(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_call(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node);
-static njs_int_t njs_generate_move_arguments(njs_vm_t *vm,
-    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_try_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_try_left(njs_vm_t *vm, njs_generator_t *generator,
@@ -5128,85 +5139,100 @@ 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_parser_node_t   *this_object;
+    njs_parser_node_t  *callee, *this_object;
 
-    var = NULL;
+    callee = node->left;
     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;
-        }
+    njs_assert(callee != NULL);
 
-        if (this_object != NULL
-            && node->left->token_type != NJS_TOKEN_OPTIONAL_CHAIN)
-        {
-            njs_internal_error(vm, "unexpected function call this");
-            return NJS_ERROR;
-        }
+    if (njs_generate_is_property_call_source(callee)) {
+        njs_internal_error(vm, "unexpected function call source");
+        return NJS_ERROR;
+    }
 
-        /* Generate function code in function expression. */
+    if (this_object != NULL
+        && callee->token_type != NJS_TOKEN_OPTIONAL_CHAIN)
+    {
+        njs_internal_error(vm, "unexpected function call this");
+        return NJS_ERROR;
+    }
 
-        njs_generator_next(generator, njs_generate, node->left);
+    njs_generator_next(generator, njs_generate, callee);
 
-        return njs_generator_after(vm, generator,
-                                   njs_queue_first(&generator->stack), node,
-                                   njs_generate_function_call_arguments,
-                                   NULL, 0);
+    return njs_generator_after(vm, generator,
+                               njs_queue_first(&generator->stack), node,
+                               njs_generate_function_call_arguments,
+                               NULL, 0);
+}
+
+
+static njs_int_t
+njs_generate_function_call_arguments(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t  ret;
+
+    ret = njs_generate_capture_stable_value(vm, generator, node->left);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
-    ret = njs_generate_variable(vm, generator, node, NJS_REFERENCE, &var);
+    if (node->right == NULL) {
+        return njs_generate_function_call_frame(vm, generator, node);
+    }
+
+    ret = njs_generator_after(vm, generator,
+                              njs_queue_first(&generator->stack), node,
+                              njs_generate_function_call_frame, NULL, 0);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
-    return njs_generate_function_call_arguments(vm, generator, node);
+    return njs_generate_call_argument_expressions(vm, generator, node->right);
 }
 
 
 static njs_int_t
-njs_generate_function_call_arguments(njs_vm_t *vm, njs_generator_t *generator,
+njs_generate_function_call_frame(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_int_t                    ret;
-    njs_jump_off_t               func_offset;
-    njs_parser_node_t            *name;
+    njs_uint_t                   nargs;
+    njs_parser_node_t            *callee, *this_object;
+    njs_parser_node_t            *arg;
+    njs_vmcode_1addr_t           *put_arg;
+    njs_vmcode_method_frame_t    *method_frame;
     njs_vmcode_function_frame_t  *func;
 
-    name = node;
+    callee = node->left;
+    njs_assert(callee != NULL);
 
-    if (node->left != NULL) {
-        name = node->left;
-    }
-
-    njs_generate_code(generator, njs_vmcode_function_frame_t, func,
-                      NJS_VMCODE_FUNCTION_FRAME, node);
-    func_offset = njs_code_offset(generator, func);
-    func->ctor = node->ctor;
-    func->name = name->index;
-    func->nargs = 0;
+    this_object = njs_generate_function_call_this(node);
+    nargs = njs_generate_call_nargs(node->right);
 
-    njs_generator_next(generator, njs_generate,
-                       (node->right != NULL ? node->right->left : NULL));
+    if (this_object != NULL) {
+        njs_generate_code(generator, njs_vmcode_method_frame_t, method_frame,
+                          NJS_VMCODE_METHOD_FRAME, node);
+        method_frame->ctor = node->ctor;
+        method_frame->function = callee->index;
+        method_frame->this_object = this_object->index;
+        method_frame->nargs = nargs;
 
-    ret = njs_generator_after(vm, generator,
-                              njs_queue_first(&generator->stack), node,
-                              njs_generate_function_call_end, NULL, 0);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
+    } else {
+        njs_generate_code(generator, njs_vmcode_function_frame_t, func,
+                          NJS_VMCODE_FUNCTION_FRAME, node);
+        func->ctor = node->ctor;
+        func->name = callee->index;
+        func->nargs = nargs;
     }
 
-    if (node->right == NULL) {
-        return NJS_OK;
+    for (arg = node->right; arg != NULL; arg = arg->right) {
+        njs_generate_code(generator, njs_vmcode_1addr_t, put_arg,
+                          NJS_VMCODE_PUT_ARG, arg);
+        put_arg->index = arg->left->index;
     }
 
-    return njs_generator_after(vm, generator,
-                               njs_queue_first(&generator->stack), node->right,
-                               njs_generate_move_arguments,
-                               &func_offset, sizeof(njs_jump_off_t));
+    return njs_generate_function_call_end(vm, generator, node);
 }
 
 
@@ -5214,8 +5240,8 @@ 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_parser_node_t   *this_object;
+    njs_int_t          ret;
+    njs_parser_node_t  *this_object;
 
     ret = njs_generate_call(vm, generator, node);
     if (njs_fast_path(ret != NJS_OK)) {
@@ -5230,7 +5256,7 @@ njs_generate_function_call_end(njs_vm_t *vm, njs_generator_t *generator,
         }
     }
 
-    return njs_generator_stack_pop(vm, generator, generator->context);
+    return njs_generator_stack_pop(vm, generator, NULL);
 }
 
 
@@ -5275,44 +5301,105 @@ static njs_int_t
 njs_generate_method_call_arguments(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_int_t                  ret;
-    njs_jump_off_t             method_offset;
-    njs_parser_node_t          *prop;
-    njs_vmcode_method_frame_t  *method;
+    njs_int_t          ret;
+    njs_parser_node_t  *prop, *property;
 
     prop = node->left;
+    property = prop->right;
 
     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);
-    method->ctor = node->ctor;
-    method->object = prop->left->index;
-    method->method = prop->right->index;
-    method->nargs = 0;
+    if (node->right == NULL) {
+        prop->index = njs_generate_node_temp_index_get(vm, generator, prop);
+        if (njs_slow_path(prop->index == NJS_INDEX_ERROR)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_generate_property_get(vm, generator, property, prop->index,
+                                        prop->left->index, property->index);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        return njs_generate_method_call_frame(vm, generator, node);
+    }
 
-    njs_generator_next(generator, njs_generate,
-                       (node->right != NULL ? node->right->left : node->right));
+    ret = njs_generate_method_call_prepare(vm, generator, node);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
 
     ret = njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
-                              njs_generate_method_call_end, NULL, 0);
+                              njs_generate_method_call_frame, NULL, 0);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
-    if (node->right == NULL) {
-        return NJS_OK;
+    return njs_generate_call_argument_expressions(vm, generator, node->right);
+}
+
+
+static njs_int_t
+njs_generate_method_call_prepare(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t          ret;
+    njs_parser_node_t  *prop, *property;
+
+    prop = node->left;
+    property = prop->right;
+
+    ret = njs_generate_capture_stable_value(vm, generator, prop->left);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
-    return njs_generator_after(vm, generator,
-                               njs_queue_first(&generator->stack), node->right,
-                               njs_generate_move_arguments,
-                               &method_offset, sizeof(njs_jump_off_t));
+    prop->index = njs_generate_node_temp_index_get(vm, generator, prop);
+    if (njs_slow_path(prop->index == NJS_INDEX_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    return njs_generate_property_get(vm, generator, property, prop->index,
+                                     prop->left->index, property->index);
+}
+
+
+static njs_int_t
+njs_generate_method_call_frame(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_uint_t                 nargs;
+    njs_parser_node_t          *arg;
+    njs_parser_node_t          *prop;
+    njs_vmcode_1addr_t         *put_arg;
+    njs_vmcode_method_frame_t  *method_frame;
+
+    prop = node->left;
+    nargs = njs_generate_call_nargs(node->right);
+
+    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_frame,
+                      NJS_VMCODE_METHOD_FRAME, node);
+    method_frame->ctor = node->ctor;
+    method_frame->function = prop->index;
+    method_frame->this_object = prop->left->index;
+    method_frame->nargs = nargs;
+
+    for (arg = node->right; arg != NULL; arg = arg->right) {
+        njs_generate_code(generator, njs_vmcode_1addr_t, put_arg,
+                          NJS_VMCODE_PUT_ARG, arg);
+        put_arg->index = arg->left->index;
+    }
+
+    return njs_generate_method_call_end(vm, generator, node);
 }
 
 
@@ -5327,7 +5414,7 @@ njs_generate_method_call_end(njs_vm_t *vm, njs_generator_t *generator,
         return ret;
     }
 
-    return njs_generator_stack_pop(vm, generator, generator->context);
+    return njs_generator_stack_pop(vm, generator, NULL);
 }
 
 
@@ -5354,36 +5441,76 @@ njs_generate_call(njs_vm_t *vm, njs_generator_t *generator,
 
 
 static njs_int_t
-njs_generate_move_arguments(njs_vm_t *vm, njs_generator_t *generator,
+njs_generate_call_argument_expressions(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_jump_off_t               func_offset;
-    njs_vmcode_1addr_t           *put_arg;
-    njs_vmcode_function_frame_t  *func;
-
     if (node == NULL) {
-        return njs_generator_stack_pop(vm, generator, generator->context);
+        return njs_generator_stack_pop(vm, generator, NULL);
     }
 
-    njs_generate_code(generator, njs_vmcode_1addr_t, put_arg,
-                      NJS_VMCODE_PUT_ARG, node);
-    put_arg->index = node->left->index;
+    njs_generator_next(generator, njs_generate, node->left);
 
-    func_offset = *((njs_jump_off_t *) generator->context);
-    func = njs_code_ptr(generator, njs_vmcode_function_frame_t, func_offset);
+    return njs_generator_after(vm, generator,
+                               njs_queue_first(&generator->stack), node,
+                               njs_generate_call_argument_expressions_after,
+                               NULL, 0);
+}
 
-    func->nargs++;
 
+static njs_int_t
+njs_generate_call_argument_expressions_after(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node)
+{
     if (node->right == NULL) {
-        return njs_generator_stack_pop(vm, generator, generator->context);
+        return njs_generator_stack_pop(vm, generator, NULL);
     }
 
     njs_generator_next(generator, njs_generate, node->right->left);
 
     return njs_generator_after(vm, generator,
                                njs_queue_first(&generator->stack), node->right,
-                               njs_generate_move_arguments,
-                               generator->context, 0);
+                               njs_generate_call_argument_expressions_after,
+                               NULL, 0);
+}
+
+
+static njs_uint_t
+njs_generate_call_nargs(njs_parser_node_t *node)
+{
+    njs_uint_t  nargs;
+
+    nargs = 0;
+
+    while (node != NULL) {
+        nargs++;
+        node = node->right;
+    }
+
+    return nargs;
+}
+
+
+static njs_int_t
+njs_generate_capture_stable_value(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_index_t         src;
+    njs_vmcode_move_t  *move;
+
+    if (node->temporary) {
+        return NJS_OK;
+    }
+
+    src = node->index;
+
+    node->index = njs_generate_node_temp_index_get(vm, generator, node);
+    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    njs_generate_code_move(generator, move, node->index, src, node);
+
+    return NJS_OK;
 }
 
 
index 9177ac2610969170bc8ff485d0434245fac8fe3a..0ca33486a5121867f766db03933a96dddfe1db10 100644 (file)
@@ -2761,30 +2761,20 @@ njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node,
 
     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:
-        /*
-         * NJS_TOKEN_METHOD_CALL,
-         * NJS_TOKEN_FUNCTION_CALL,
-         * NJS_TOKEN_FUNCTION_EXPRESSION,
-         * NJS_TOKEN_OPEN_PARENTHESIS,
-         * NJS_TOKEN_EVAL.
-         */
-        func = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_CALL);
-        if (func == NULL) {
-            return NULL;
-        }
-
-        func->left = node;
-        break;
+    /*
+     * NJS_TOKEN_NAME,
+     * NJS_TOKEN_METHOD_CALL,
+     * NJS_TOKEN_FUNCTION_CALL,
+     * NJS_TOKEN_FUNCTION_EXPRESSION,
+     * NJS_TOKEN_OPEN_PARENTHESIS,
+     * NJS_TOKEN_EVAL.
+     */
+    func = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_CALL);
+    if (func == NULL) {
+        return NULL;
     }
 
+    func->left = node;
     func->ctor = ctor;
     njs_parser_set_call_this(func, this_object);
 
index 32a1890d99a4a14c0aea5793dde1abc2386b5577..c4bbeff61e97e0fe7ba0452956fd6ddcf98ff22c 100644 (file)
@@ -87,7 +87,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
     double                       num, exponent;
     int32_t                      i32;
     uint32_t                     u32;
-    njs_str_t                    string;
     njs_bool_t                   valid, lambda_call;
     njs_value_t                  *retval, *value1, *value2;
     njs_value_t                  *src, *s1, *s2, dst;
@@ -1509,39 +1508,8 @@ NEXT_LBL;
 
         method_frame = (njs_vmcode_method_frame_t *) pc;
 
-        if (njs_slow_path(!njs_is_key(value2))) {
-            if (njs_slow_path(njs_is_null_or_undefined(value1))) {
-                (void) njs_throw_cannot_property(vm, value1, value2, "get");
-                goto error;
-            }
-
-            ret = njs_value_to_key(vm, &primitive1, value2);
-            if (njs_slow_path(ret != NJS_OK)) {
-                goto error;
-            }
-
-            value2 = &primitive1;
-        }
-
-        ret = njs_value_property_val(vm, value1, value2, &dst);
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            goto error;
-        }
-
-        if (njs_slow_path(!njs_is_function(&dst))) {
-            ret = njs_value_to_key(vm, &dst, value2);
-            if (njs_slow_path(ret != NJS_OK)) {
-                goto error;
-            }
-
-            njs_key_string_get(vm, &dst, &string);
-            njs_type_error(vm,
-                       "(intermediate value)[\"%V\"] is not a function",
-                       &string);
-            goto error;
-        }
-
-        ret = njs_function_frame_create(vm, &dst, value1, method_frame->nargs,
+        ret = njs_function_frame_create(vm, value1, value2,
+                                        method_frame->nargs,
                                         method_frame->ctor);
 
         if (njs_slow_path(ret != NJS_OK)) {
index 2cdfd23b8b95657cffc99fd62e428a16e9f615fa..077eb745f41dc2fc16256f94991bbcab0f64ce9e 100644 (file)
@@ -296,8 +296,8 @@ typedef struct {
 typedef struct {
     njs_vmcode_t               code;
     njs_index_t                nargs;
-    njs_index_t                object;
-    njs_index_t                method;
+    njs_index_t                function;
+    njs_index_t                this_object;
     uint8_t                    ctor;       /* 1 bit  */
 } njs_vmcode_method_frame_t;
 
index 38120516d75560bbb3c07fcfa5094c476a624d96..e718c147f2a2431e15f6d074cd2215fe1b43d117 100644 (file)
@@ -168,6 +168,77 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("function f(a) {return function (x) {return a(x)}} f(1)(0)"),
       njs_str("TypeError: number is not a function") },
 
+    { njs_str("var fooCalled = false;"
+              "function foo(){ fooCalled = true; }"
+              "var o = {}, r;"
+              "try { o.bar(foo()) } catch (e) { r = e.name + ':' + fooCalled }"
+              "r"),
+      njs_str("TypeError:true") },
+
+    { njs_str("var o = {}, r;"
+              "Object.defineProperty(o, 'bar', {"
+              "    get: function() { this.barGetter = true; return 42; }"
+              "});"
+              "try { o.foo(o.bar) } catch (e) { r = e.name + ':' + o.barGetter }"
+              "r"),
+      njs_str("TypeError:true") },
+
+    { njs_str("var o = {}, r;"
+              "Object.defineProperty(o, 'bar', {"
+              "    get: function() { this.barGetter = true; return 42; }"
+              "});"
+              "try { o.foo(o['bar']) } catch (e) {"
+              "    r = e.name + ':' + o.barGetter"
+              "}"
+              "r"),
+      njs_str("TypeError:true") },
+
+    { njs_str("var fooCalled = false;"
+              "function foo(){ fooCalled = true; }"
+              "var o = {}, r;"
+              "try { o.bar.gar(foo()) } catch (e) {"
+              "    r = e.name + ':' + fooCalled"
+              "}"
+              "r"),
+      njs_str("TypeError:false") },
+
+    { njs_str("var x = function() { this.foo = 42; };"
+              "var result = new x(x = 1);"
+              "[x, result.foo]"),
+      njs_str("1,42") },
+
+    { njs_str("function fn() {"
+              "    var x = function() { this.foo = 42; };"
+              "    var result = new x(x = 1);"
+              "    return [x, result.foo];"
+              "}"
+              "fn()"),
+      njs_str("1,42") },
+
+    { njs_str("var C = {}; var ran = false, r;"
+              "try { new C((ran = true, 1)) } catch (e) {"
+              "    r = e.name + ':' + ran"
+              "}"
+              "r"),
+      njs_str("TypeError:true") },
+
+    { njs_str("var o = {"
+              "    get f() { this.hit = (this.hit || 0) + 1; return 1; }"
+              "};"
+              "var ran = false, r;"
+              "try { o.f((ran = true, 1)) } catch (e) {"
+              "    r = ran + ':' + o.hit"
+              "}"
+              "r"),
+      njs_str("true:1") },
+
+    { njs_str("var o = {"
+              "    get f() { return function(v) { return this.x + v } },"
+              "    x: 7"
+              "};"
+              "o.f(5)"),
+      njs_str("12") },
+
     { njs_str("var x = 0;"
               ""
               "function f1() {"
@@ -1866,6 +1937,30 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var o = {m: function() {return 42}}; (o?.m)()"),
       njs_str("42") },
 
+    { njs_str("var o = {x: 5, m: function() {return this.x}}; (o?.m)()"),
+      njs_str("5") },
+
+    { njs_str("var o = {x: 3, m: function() {return {c: this.x}}};"
+              "o.m?.().c"),
+      njs_str("3") },
+
+    { njs_str("var o = {x: 4, m: function() {return {c: this.x}}};"
+              "(o.m)?.().c"),
+      njs_str("4") },
+
+    { njs_str("var o = {x: 6, m: function() {return {c: this.x}}};"
+              "o?.m?.().c"),
+      njs_str("6") },
+
+    { njs_str("var o = {x: 8, m: function() {return {c: this.x}}};"
+              "(o?.m)?.().c"),
+      njs_str("8") },
+
+    { njs_str("var k = 'm';"
+              "var o = {x: 9, m: function() {return this.x}};"
+              "(o?.[k])()"),
+      njs_str("9") },
+
     { njs_str("var o = null; (o?.m)()"),
       njs_str("TypeError: undefined is not a function") },
 
@@ -2630,10 +2725,10 @@ static njs_unit_test_t  njs_test[] =
       njs_str("false") },
 
     { njs_str("new 0[isNaN]"),
-      njs_str("TypeError: (intermediate value)[\"[object Function]\"] is not a function") },
+      njs_str("TypeError: undefined is not a function") },
 
     { njs_str("new 0[undefined]"),
-      njs_str("TypeError: (intermediate value)[\"undefined\"] is not a function") },
+      njs_str("TypeError: undefined is not a function") },
 
     /**/
 
@@ -4016,7 +4111,7 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("function f() { Object.prototype.toString = 1; };"
               "Object.prototype.toString = f;"
               "(function () { try { 's'[{}](); } catch (e) { throw e; } })()"),
-      njs_str("TypeError: (intermediate value)[\"undefined\"] is not a function") },
+      njs_str("TypeError: undefined is not a function") },
 
     { njs_str("var i; for (i = 0; i < 10; i++) { i += 1 } i"),
       njs_str("10") },
@@ -10825,7 +10920,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("TypeError: number is not a function") },
 
     { njs_str("var o = {a:1}; o.a()"),
-      njs_str("TypeError: (intermediate value)[\"a\"] is not a function") },
+      njs_str("TypeError: number is not a function") },
 
     { njs_str("(function(){})()"),
       njs_str("undefined") },
@@ -12864,10 +12959,10 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: Unexpected token \"null\"") },
 
     { njs_str("'a'.f()"),
-      njs_str("TypeError: (intermediate value)[\"f\"] is not a function") },
+      njs_str("TypeError: undefined is not a function") },
 
     { njs_str("1..f()"),
-      njs_str("TypeError: (intermediate value)[\"f\"] is not a function") },
+      njs_str("TypeError: undefined is not a function") },
 
     { njs_str("try {}"),
       njs_str("SyntaxError: Missing catch or finally after try") },
@@ -21689,7 +21784,7 @@ static njs_unit_test_t  njs_shared_test[] =
               "    at main (:1)\n") },
 
     { njs_str("preload.a.push(2)"),
-      njs_str("TypeError: (intermediate value)[\"push\"] is not a function\n"
+      njs_str("TypeError: undefined is not a function\n"
               "    at main (:1)\n") },
 
     { njs_str("Array.prototype.push.call(preload.a, 'waka')"),
@@ -22058,7 +22153,6 @@ static njs_unit_test_t  njs_backtraces_test[] =
 
     { njs_str("[].concat({}.a.a)"),
       njs_str("TypeError: cannot get property \"a\" of undefined\n"
-              "    at concat (native)\n"
               "    at main (:1)\n") },
 
     { njs_str("''.repeat(-1)"),
@@ -22068,7 +22162,6 @@ static njs_unit_test_t  njs_backtraces_test[] =
 
     { njs_str("Math.log({}.a.a)"),
       njs_str("TypeError: cannot get property \"a\" of undefined\n"
-              "    at log (native)\n"
               "    at main (:1)\n") },
 
     { njs_str("var bound = Math.max.bind(null, {toString(){return {}}}); bound(1)"),
@@ -22084,7 +22177,7 @@ static njs_unit_test_t  njs_backtraces_test[] =
               "    at main (:1)\n") },
 
     { njs_str("Object.prototype()"),
-      njs_str("TypeError: (intermediate value)[\"prototype\"] is not a function\n"
+      njs_str("TypeError: object is not a function\n"
                "    at main (:1)\n") },
 
     { njs_str("eval()"),
@@ -22094,7 +22187,6 @@ static njs_unit_test_t  njs_backtraces_test[] =
 
     { njs_str("$shared.method({}.a.a)"),
       njs_str("TypeError: cannot get property \"a\" of undefined\n"
-              "    at method (native)\n"
               "    at main (:1)\n") },
 
     { njs_str("new Function(\n\n@)"),
@@ -22227,7 +22319,7 @@ static njs_unit_test_t  njs_backtraces_test[] =
 
     { njs_str("function log(v) {}\nlog({}\n.a\n.a)"),
       njs_str("TypeError: cannot get property \"a\" of undefined\n"
-              "    at main (:4)\n") },
+              "    at main (:2)\n") },
 
     { njs_str("\nfor (var i = 0;\n i < a;\n i++) { }\n"),
       njs_str("ReferenceError: \"a\" is not defined\n"
@@ -22239,7 +22331,6 @@ static njs_unit_test_t  njs_backtraces_test[] =
 
     { njs_str("Math\n.min(1,\na)"),
       njs_str("ReferenceError: \"a\" is not defined\n"
-              "    at min (native)\n"
               "    at main (:3)\n") },
 };
 
index d0f2d7753973ce1e53401cf956d59a844044b412..56d3f15867ec9c33b966fd8a178d07b6a739d687 100644 (file)
@@ -56,7 +56,7 @@ njs_test {
 
 njs_test {
     {"console.ll()\r\n"
-     "console.ll()\r\nThrown:\r\nTypeError: (intermediate value)\\\[\"ll\"] is not a function"}
+     "console.ll()\r\nThrown:\r\nTypeError: undefined is not a function"}
 }
 
 njs_test {
@@ -67,7 +67,7 @@ njs_test {
 # Backtraces for external objects
 njs_test {
     {"console.info(console.a.a)\r\n"
-     "console.info(console.a.a)\r\nThrown:\r\nTypeError:*at info (native)"}
+     "console.info(console.a.a)\r\nThrown:\r\nTypeError:*at main (shell:1)"}
 }
 
 # dumper