]> git.kaiwu.me - njs.git/commitdiff
Fixed for-in loop with left and right hand side expressions.
authorVadim Zhestikov <v.zhestikov@f5.com>
Mon, 14 Nov 2022 17:18:37 +0000 (09:18 -0800)
committerVadim Zhestikov <v.zhestikov@f5.com>
Mon, 14 Nov 2022 17:18:37 +0000 (09:18 -0800)
This fixes #351 issue on Github.

src/njs_generator.c
src/njs_lexer.c
src/njs_lexer.h
src/njs_parser.c
src/njs_parser.h
src/test/njs_unit_test.c

index 6d83a9e5828275c99737e9db8ee69a4c840a3b51..2364a3ddb5320896b0ec29cf9e794abe0246743e 100644 (file)
@@ -86,6 +86,7 @@ typedef struct {
     njs_vmcode_jump_t           *jump;
     njs_variable_t              *var;
     njs_index_t                 index;
+    njs_index_t                 index_next_value;
 } njs_generator_loop_ctx_t;
 
 
@@ -176,10 +177,22 @@ static njs_int_t njs_generate_for_resolve_closure(njs_vm_t *vm,
     njs_parser_node_t *node);
 static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_set_prop_block(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_name_assign(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_for_in_object(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_object_wo_decl(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_object_left_hand_expr(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_for_in_body(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_body_wo_decl(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_in_body_left_hand_expr(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_start_block(njs_vm_t *vm,
     njs_generator_t *generator, njs_generator_block_type_t type,
     const njs_str_t *label);
@@ -1993,6 +2006,181 @@ njs_generate_for_resolve_closure(njs_vm_t *vm, njs_parser_node_t *node)
 }
 
 
+static njs_int_t
+njs_generate_for_in_name_assign(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         *foreach, *lvalue, *expr;
+    njs_vmcode_move_t         *move;
+    njs_generator_loop_ctx_t  *ctx;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+    lvalue = foreach->left;
+    expr = node->right;
+
+    var = njs_variable_reference(vm, lvalue);
+
+    if (var != NULL) {
+        ctx->index_next_value = lvalue->index;
+
+    } else {
+        ctx->index_next_value = njs_generate_temp_index_get(vm, generator,
+                                                            foreach->left);
+        if (njs_slow_path(ctx->index_next_value == NJS_INDEX_ERROR)) {
+            return NJS_ERROR;
+        }
+
+        if (expr != NULL) {
+            expr->index = ctx->index_next_value;
+
+            /*
+             * lvalue and expression indexes are equal if the expression is an
+             * empty object or expression result is stored directly in variable.
+             */
+            if (lvalue->index != expr->index) {
+                njs_generate_code_move(generator, move, lvalue->index,
+                                       expr->index, expr);
+            }
+
+            ret = njs_generate_global_property_set(vm, generator, foreach->left,
+                                                   expr);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+        }
+    }
+    return njs_generator_stack_pop(vm, generator, NULL);
+}
+
+
+static njs_int_t
+njs_generate_for_in_body_wo_decl(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t                 ret;
+    njs_jump_off_t            prop_offset;
+    njs_parser_node_t         *foreach, *name;
+    njs_vmcode_prop_next_t    *prop_next;
+    njs_generator_loop_ctx_t  *ctx;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+    name = foreach->left->right;
+
+    /* The loop iterator. */
+
+    if (name != NULL) {
+        ret = njs_generate_for_let_update(vm, generator, foreach->left);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    njs_generate_patch_block(vm, generator, generator->block,
+                             NJS_GENERATOR_CONTINUATION);
+
+    njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t,
+                             ctx->jump_offset);
+
+    njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next,
+                      NJS_VMCODE_PROPERTY_NEXT, 3, node->left->left);
+    prop_offset = njs_code_offset(generator, prop_next);
+    prop_next->retval = ctx->index_next_value;
+    prop_next->object = foreach->right->index;
+    prop_next->next = ctx->index;
+    prop_next->offset = ctx->loop_offset - prop_offset;
+
+    njs_generate_patch_block_exit(vm, generator);
+
+    /*
+     * Release object and iterator indexes: an object can be a function result
+     * or a property of another object and an iterator can be given with "let".
+     */
+    ret = njs_generate_children_indexes_release(vm, generator, foreach);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ret = njs_generate_index_release(vm, generator, ctx->index);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    return njs_generator_stack_pop(vm, generator, ctx);
+}
+
+
+static njs_int_t
+njs_generate_for_in_object_wo_decl(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t                  ret;
+    njs_parser_node_t          *foreach, *name;
+    njs_generator_loop_ctx_t   *ctx;
+    njs_vmcode_prop_foreach_t  *prop_foreach;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+    name = foreach->left->right;
+
+    if (name != NULL) {
+        ctx->var->init = 1;
+    }
+
+    njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
+                      NJS_VMCODE_PROPERTY_FOREACH, 2, foreach);
+    ctx->jump_offset = njs_code_offset(generator, prop_foreach);
+    prop_foreach->object = foreach->right->index;
+
+    ctx->index = njs_generate_temp_index_get(vm, generator, foreach->right);
+    if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    prop_foreach->next = ctx->index;
+
+    /* The loop body. */
+
+    ctx->loop_offset = njs_code_offset(generator, generator->code_end);
+
+
+    /* 1) left. */
+
+    njs_generator_next(generator, njs_generate, foreach->left);
+
+    /* 4) loop-body-end. */
+
+    ret = njs_generator_after(vm, generator,
+                               njs_queue_first(&generator->stack), node,
+                               njs_generate_for_in_body_wo_decl, ctx, 0);
+    if (ret != NJS_OK) {
+        return ret;
+    }
+
+    /* 3) loop-body. */
+
+    ret = njs_generator_after(vm, generator,
+                               njs_queue_first(&generator->stack), node->right,
+                               njs_generate, ctx, 0);
+    if (ret != NJS_OK) {
+        return ret;
+    }
+
+    /* 2) assign value to name. */
+
+    return njs_generator_after(vm, generator,
+                               njs_queue_first(&generator->stack), node,
+                               njs_generate_for_in_name_assign, ctx, 0);
+
+}
+
+
 static njs_int_t
 njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
@@ -2010,40 +2198,135 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
     /* The object. */
 
     foreach = node->left;
-    name = foreach->left->right;
 
-    if (name != NULL) {
-        name = name->left;
+    if (foreach->left->token_type != NJS_TOKEN_PROPERTY) {
+        name = foreach->left->right;
 
-        ret = njs_generate_variable_wo_dest(vm, generator, name,
+        if (name != NULL) {
+            name = name->left;
+
+            ret = njs_generate_variable_wo_dest(vm, generator, name,
                                             NJS_DECLARATION, &ctx.var);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return NJS_ERROR;
+            if (njs_slow_path(ret != NJS_OK)) {
+                return NJS_ERROR;
+            }
+
+            foreach->left->index = name->index;
+
+            njs_generator_next(generator, njs_generate, foreach->right);
+
+            return njs_generator_after(vm, generator,
+                                       njs_queue_first(&generator->stack), node,
+                                       njs_generate_for_in_object,
+                                       &ctx, sizeof(njs_generator_loop_ctx_t));
         }
 
-        foreach->left->index = name->index;
+    } else {
+
+        /* foreach->right is object in 'in object'. */
 
         njs_generator_next(generator, njs_generate, foreach->right);
 
         return njs_generator_after(vm, generator,
                                    njs_queue_first(&generator->stack), node,
-                                   njs_generate_for_in_object,
+                                   njs_generate_for_in_object_left_hand_expr,
                                    &ctx, sizeof(njs_generator_loop_ctx_t));
+
     }
 
-    njs_generator_next(generator, njs_generate, foreach->left);
+    njs_generator_next(generator, njs_generate, foreach->right);
 
-    ret = njs_generator_after(vm, generator,
+    return  njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
-                              njs_generate_for_in_object,
+                              njs_generate_for_in_object_wo_decl,
                               &ctx, sizeof(njs_generator_loop_ctx_t));
+}
+
+
+static njs_int_t
+njs_generate_for_in_object_left_hand_expr(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node)
+{
+    njs_int_t                 ret;
+    njs_parser_node_t          *foreach;
+    njs_generator_loop_ctx_t   *ctx;
+    njs_vmcode_prop_foreach_t  *prop_foreach;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+
+    njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
+                      NJS_VMCODE_PROPERTY_FOREACH, 2, foreach);
+    ctx->jump_offset = njs_code_offset(generator, prop_foreach);
+    prop_foreach->object = foreach->right->index;
+
+    ctx->index = njs_generate_temp_index_get(vm, generator, foreach->right);
+    if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    ctx->index_next_value = njs_generate_temp_index_get(vm, generator,
+                                                        foreach->left);
+    if (njs_slow_path(ctx->index_next_value == NJS_INDEX_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    prop_foreach->next = ctx->index;
+
+    ctx->loop_offset = njs_code_offset(generator, generator->code_end);
+
+    /* Object part calculation. */
+
+    njs_generator_next(generator, njs_generate, foreach->left->left);
+
+    /* The loop body. */
+
+    ret = njs_generator_after(vm, generator, njs_queue_first(&generator->stack),
+                              node, njs_generate_for_in_body_left_hand_expr,
+                              ctx, sizeof(njs_generator_loop_ctx_t));
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
+    /* set-property and block. */
+
+    ret = njs_generator_after(vm, generator, njs_queue_first(&generator->stack),
+                              node, njs_generate_for_in_set_prop_block, ctx,
+                              sizeof(njs_generator_loop_ctx_t));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    /* Key part calculation. */
+
     return njs_generator_after(vm, generator,
                                njs_queue_first(&generator->stack),
-                               foreach->right, njs_generate, NULL, 0);
+                               foreach->left->right, njs_generate, NULL, 0);
+}
+
+
+static njs_int_t
+njs_generate_for_in_set_prop_block(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_parser_node_t      *foreach;
+    njs_vmcode_prop_set_t  *prop_set;
+    njs_generator_loop_ctx_t   *ctx;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+
+    njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
+                      NJS_VMCODE_PROPERTY_SET, 3, foreach);
+    prop_set->object = foreach->left->left->index;
+    prop_set->property = foreach->left->right->index;
+    prop_set->value = ctx->index_next_value;
+
+    njs_generator_next(generator, njs_generate, node->right);
+
+    return NJS_OK;
 }
 
 
@@ -2088,6 +2371,54 @@ njs_generate_for_in_object(njs_vm_t *vm, njs_generator_t *generator,
 }
 
 
+static njs_int_t
+njs_generate_for_in_body_left_hand_expr(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node)
+{
+    njs_int_t                 ret;
+    njs_jump_off_t            prop_offset;
+    njs_parser_node_t         *foreach;
+    njs_vmcode_prop_next_t    *prop_next;
+    njs_generator_loop_ctx_t  *ctx;
+
+    ctx = generator->context;
+
+    foreach = node->left;
+
+    njs_generate_patch_block(vm, generator, generator->block,
+                             NJS_GENERATOR_CONTINUATION);
+
+    njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t,
+                             ctx->jump_offset);
+
+    njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next,
+                      NJS_VMCODE_PROPERTY_NEXT, 3, node->left->left);
+    prop_offset = njs_code_offset(generator, prop_next);
+    prop_next->retval = ctx->index_next_value;
+    prop_next->object = foreach->right->index;
+    prop_next->next = ctx->index;
+    prop_next->offset = ctx->loop_offset - prop_offset;
+
+    njs_generate_patch_block_exit(vm, generator);
+
+    /*
+     * Release object and iterator indexes: an object can be a function result
+     * or a property of another object and an iterator can be given with "let".
+     */
+    ret = njs_generate_children_indexes_release(vm, generator, foreach);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ret = njs_generate_index_release(vm, generator, ctx->index);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    return njs_generator_stack_pop(vm, generator, ctx);
+}
+
+
 static njs_int_t
 njs_generate_for_in_body(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
index 91b08e94155ff447aa5370e0a49dc3d79003d6b4..d44d26e7996a222eaf55d03dbd1c0dd984f4549d 100644 (file)
@@ -290,9 +290,13 @@ static const njs_lexer_multi_t  njs_assignment_token[] = {
 
 njs_int_t
 njs_lexer_init(njs_vm_t *vm, njs_lexer_t *lexer, njs_str_t *file,
-    u_char *start, u_char *end, njs_uint_t runtime)
+    u_char *start, u_char *end, njs_uint_t runtime,
+    njs_int_t init_lexer_memory)
 {
-    njs_memzero(lexer, sizeof(njs_lexer_t));
+    if (init_lexer_memory) {
+        njs_memzero(lexer, sizeof(njs_lexer_t));
+
+    }
 
     lexer->file = *file;
     lexer->start = start;
@@ -304,6 +308,105 @@ njs_lexer_init(njs_vm_t *vm, njs_lexer_t *lexer, njs_str_t *file,
 
     njs_queue_init(&lexer->preread);
 
+    return njs_lexer_in_stack_init(lexer);
+}
+
+
+njs_int_t
+njs_lexer_in_stack_init(njs_lexer_t *lexer)
+{
+    lexer->in_stack_size = 128;
+    lexer->in_stack = njs_mp_zalloc(lexer->mem_pool, lexer->in_stack_size);
+    if (lexer->in_stack == NULL) {
+        return NJS_ERROR;
+    }
+
+    lexer->in_stack_ptr = 0;
+
+    return NJS_OK;
+}
+
+
+njs_int_t
+njs_lexer_in_stack_push(njs_lexer_t *lexer)
+{
+    u_char  *tmp;
+    size_t  size;
+
+    lexer->in_stack_ptr++;
+
+    if (lexer->in_stack_ptr < lexer->in_stack_size) {
+        lexer->in_stack[lexer->in_stack_ptr] = 0;
+        return NJS_OK;
+    }
+
+    /* Realloc in_stack, it is up to higher layer generate error if any. */
+
+    size = lexer->in_stack_size;
+    lexer->in_stack_size = size * 2;
+
+    tmp = njs_mp_alloc(lexer->mem_pool, size * 2);
+    if (tmp == NULL) {
+        return NJS_ERROR;
+    }
+
+    memcpy(tmp, lexer->in_stack, size);
+    memset(&tmp[size], 0, size);
+
+    njs_mp_free(lexer->mem_pool, lexer->in_stack);
+    lexer->in_stack = tmp;
+
+    return NJS_OK;
+}
+
+
+void
+njs_lexer_in_stack_pop(njs_lexer_t *lexer)
+{
+    /**
+     * if in_stack_ptr <= 0 do nothing, it is up to higher layer
+     * generate error.
+     */
+
+    if (lexer->in_stack_ptr > 0) {
+        lexer->in_stack_ptr--;
+    }
+}
+
+
+njs_int_t
+njs_lexer_in_fail_get(njs_lexer_t *lexer)
+{
+    return lexer->in_stack[lexer->in_stack_ptr];
+}
+
+
+void
+njs_lexer_in_fail_set(njs_lexer_t *lexer, njs_int_t flag)
+{
+    lexer->in_stack[lexer->in_stack_ptr] = flag;
+}
+
+
+njs_inline njs_int_t
+njs_lexer_in_stack(njs_lexer_t *lexer, njs_lexer_token_t  *token)
+{
+    switch (token->type) {
+    case NJS_TOKEN_OPEN_PARENTHESIS:
+    case NJS_TOKEN_OPEN_BRACKET:
+    case NJS_TOKEN_OPEN_BRACE:
+        return njs_lexer_in_stack_push(lexer);
+
+    case NJS_TOKEN_CLOSE_PARENTHESIS:
+    case NJS_TOKEN_CLOSE_BRACKET:
+    case NJS_TOKEN_CLOSE_BRACE:
+        njs_lexer_in_stack_pop(lexer);
+        break;
+
+    default:
+        break;
+    }
+
     return NJS_OK;
 }
 
@@ -329,6 +432,11 @@ njs_lexer_next_token(njs_lexer_t *lexer)
 
     njs_queue_insert_tail(&lexer->preread, &token->link);
 
+    ret = njs_lexer_in_stack(lexer, token);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NULL;
+    }
+
     return token;
 }
 
index c62c70c3faa000b1ad61595958d133cdef771054..f810551118e66d9d64c72e7527760953d3d7a26e 100644 (file)
@@ -267,11 +267,17 @@ typedef struct {
 
     u_char                          *start;
     u_char                          *end;
+
+#define NJS_INITIAL_IN_STACK_SIZE 128
+    uint8_t                        *in_stack;
+    njs_int_t                       in_stack_ptr;
+    njs_int_t                       in_stack_size;
 } njs_lexer_t;
 
 
 njs_int_t njs_lexer_init(njs_vm_t *vm, njs_lexer_t *lexer, njs_str_t *file,
-    u_char *start, u_char *end, njs_uint_t runtime);
+    u_char *start, u_char *end, njs_uint_t runtime,
+    njs_int_t init_lexer_memory);
 
 njs_lexer_token_t *njs_lexer_token(njs_lexer_t *lexer,
     njs_bool_t with_end_line);
@@ -279,6 +285,12 @@ njs_lexer_token_t *njs_lexer_peek_token(njs_lexer_t *lexer,
     njs_lexer_token_t *current, njs_bool_t with_end_line);
 void njs_lexer_consume_token(njs_lexer_t *lexer, unsigned length);
 njs_int_t njs_lexer_make_token(njs_lexer_t *lexer, njs_lexer_token_t *token);
+njs_int_t njs_lexer_in_stack_init(njs_lexer_t *lexer);
+njs_int_t njs_lexer_in_stack_push(njs_lexer_t *lexer);
+void njs_lexer_in_stack_pop(njs_lexer_t *lexer);
+void njs_lexer_in_fail_set(njs_lexer_t *lexer, njs_int_t flag);
+njs_int_t njs_lexer_in_fail_get(njs_lexer_t *lexer);
+
 
 const njs_lexer_keyword_entry_t *njs_lexer_keyword(const u_char *key,
     size_t length);
index 66d7c7c89b9101121ebc62cba6d9437dba8f69fc..ee7ce9f3871bb881292e75f661d51878cdf13ba1 100644 (file)
@@ -294,6 +294,16 @@ static njs_int_t njs_parser_while_after(njs_parser_t *parser,
 
 static njs_int_t njs_parser_iteration_statement_for(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
+static njs_int_t njs_parser_for_left_hand_side_expression_map(
+    njs_parser_t *parser, njs_lexer_token_t *token,
+    njs_queue_link_t *current);
+static njs_int_t njs_parser_expression_continue_op(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current);
+static njs_int_t njs_parser_expression_continue_assign_comma(
+    njs_parser_t *parser, njs_lexer_token_t *token,
+    njs_queue_link_t *current);
+static njs_int_t njs_parser_for_in_statement_statement(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
@@ -527,17 +537,9 @@ njs_parser_init(njs_vm_t *vm, njs_parser_t *parser, njs_parser_scope_t *scope,
     lexer = &parser->lexer0;
     parser->lexer = lexer;
 
-    lexer->file = *file;
-    lexer->start = start;
-    lexer->end = end;
-    lexer->line = 1;
-    lexer->keywords_hash = (runtime) ? &vm->keywords_hash
-                                     : &vm->shared->keywords_hash;
-    lexer->mem_pool = vm->mem_pool;
+    parser->use_lhs = 0;
 
-    njs_queue_init(&lexer->preread);
-
-    return NJS_OK;
+    return njs_lexer_init(vm, lexer, file, start, end, runtime, 0);
 }
 
 
@@ -3620,11 +3622,17 @@ njs_parser_exponentiation_expression(njs_parser_t *parser,
 {
     parser->target = NULL;
 
-    njs_parser_next(parser, njs_parser_unary_expression);
+    if (parser->use_lhs == 0) {
+        njs_parser_next(parser, njs_parser_unary_expression);
 
-    /* For UpdateExpression, see njs_parser_unary_expression_after. */
+        /* For UpdateExpression, see njs_parser_unary_expression_after. */
 
-    return NJS_OK;
+        return NJS_OK;
+    } else {
+        parser->use_lhs = 0;
+
+        return njs_parser_update_expression_post(parser, token, current);
+    }
 }
 
 
@@ -3899,6 +3907,10 @@ njs_parser_relational_expression_match(njs_parser_t *parser,
         break;
 
     case NJS_TOKEN_IN:
+        if (njs_lexer_in_fail_get(parser->lexer)) {
+            njs_parser_syntax_error(parser, "Invalid left-hand side in for-loop");
+            return NJS_ERROR;
+        }
         operation = NJS_VMCODE_PROPERTY_IN;
         break;
 
@@ -4215,6 +4227,11 @@ njs_parser_conditional_question_mark(njs_parser_t *parser,
     cond->right = node;
 
     njs_lexer_consume_token(parser->lexer, 1);
+
+    if (njs_lexer_in_stack_push(parser->lexer) != NJS_OK) {
+        return NJS_ERROR;
+    }
+
     njs_parser_next(parser, njs_parser_assignment_expression);
 
     return njs_parser_after(parser, current, cond, 1,
@@ -4232,6 +4249,8 @@ njs_parser_conditional_colon(njs_parser_t *parser, njs_lexer_token_t *token,
         return njs_parser_failed(parser);
     }
 
+    njs_lexer_in_stack_pop(parser->lexer);
+
     njs_lexer_consume_token(parser->lexer, 1);
 
     node = parser->target->right;
@@ -5469,6 +5488,175 @@ njs_parser_iteration_statement_for(njs_parser_t *parser,
 }
 
 
+static njs_int_t
+njs_parser_for_left_hand_side_expression_map(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    njs_int_t          operation;
+    njs_str_t          *text;
+    njs_parser_node_t  *node;
+
+    if (parser->node == NULL) {
+        njs_lexer_in_fail_set(parser->lexer, 1);
+
+        njs_parser_next(parser, njs_parser_expression);
+
+        /*
+         * Here we pass not a node, but a token, this is important.
+         * This is necessary for correct error output.
+         */
+
+        text = njs_mp_alloc(parser->vm->mem_pool, sizeof(njs_str_t));
+        if (text == NULL) {
+            return NJS_ERROR;
+        }
+
+        *text = token->text;
+
+        return njs_parser_after(parser, current, text, 1,
+                                njs_parser_for_var_in_of_expression);
+
+    }
+
+    if (token->type != NJS_TOKEN_IN) {
+        njs_lexer_in_fail_set(parser->lexer, 1);
+
+        /* Continue parsing of expr1 in "for (expr1;[expr2];[expr3])". */
+
+        njs_parser_next(parser, njs_parser_expression_continue_op);
+
+        /*
+         * Here we pass not a node, but a token, this is important.
+         * This is necessary for correct error output.
+         */
+
+        text = njs_mp_alloc(parser->vm->mem_pool, sizeof(njs_str_t));
+        if (text == NULL) {
+            return NJS_ERROR;
+        }
+
+        *text = token->text;
+
+        return njs_parser_after(parser, current, text, 1,
+                                njs_parser_for_var_in_of_expression);
+
+    } else {
+
+        /* for-in */
+
+        if (parser->node->token_type != NJS_TOKEN_NAME &&
+            parser->node->token_type != NJS_TOKEN_PROPERTY)
+        {
+            text = (njs_str_t *) parser->target;
+
+            njs_parser_ref_error(parser, "Invalid left-hand side \"%V\" "
+                                 "in for-in statement", text);
+
+            njs_mp_free(parser->vm->mem_pool, text);
+
+            return NJS_DONE;
+        }
+
+        operation = NJS_VMCODE_PROPERTY_IN;
+
+        node = njs_parser_node_new(parser, token->type);
+        if (node == NULL) {
+            return NJS_ERROR;
+        }
+
+        node->token_line = token->line;
+        node->u.operation = operation;
+        node->left = parser->node;
+        node->left->dest = node;
+
+        njs_lexer_consume_token(parser->lexer, 1);
+
+        njs_parser_next(parser, njs_parser_expression);
+
+        return njs_parser_after(parser, current, node, 0,
+                                njs_parser_for_in_statement_statement);
+    }
+
+}
+
+
+static njs_int_t
+njs_parser_after_expr(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    parser->target->right = parser->node;
+    parser->node = parser->target;
+
+    return njs_parser_stack_pop(parser);
+}
+
+
+static njs_int_t
+njs_parser_comma_expression_comma(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    njs_parser_node_t  *node;
+
+    if (parser->target != NULL) {
+        parser->target->right = parser->node;
+        parser->target->right->dest = parser->target;
+        parser->node = parser->target;
+    }
+
+    if (token->type != NJS_TOKEN_COMMA) {
+        return njs_parser_stack_pop(parser);
+    }
+
+    node = njs_parser_node_new(parser, NJS_TOKEN_COMMA);
+    if (node == NULL) {
+        return NJS_ERROR;
+    }
+
+    node->token_line = token->line;
+    node->u.operation = 0;
+    node->left = parser->node;
+    node->left->dest = node;
+
+    njs_lexer_consume_token(parser->lexer, 1);
+
+    njs_parser_next(parser, njs_parser_expression);
+
+    return njs_parser_after(parser, current, node, 1, njs_parser_after_expr);
+}
+
+
+static njs_int_t
+njs_parser_expression_continue_op(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    if (token->type == NJS_TOKEN_CONDITIONAL) {
+        njs_parser_next(parser, njs_parser_conditional_question_mark);
+        return njs_parser_after(parser, current, NULL, 0,
+                                njs_parser_expression_continue_assign_comma);
+    } else {
+        parser->target = NULL;
+
+        parser->use_lhs = 1;
+
+        njs_parser_next(parser, njs_parser_expression);
+
+        return njs_parser_after(parser, current, NULL, 1,
+                                njs_parser_comma_expression_comma);
+    }
+}
+
+
+static njs_int_t
+njs_parser_expression_continue_assign_comma(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    njs_parser_next(parser, njs_parser_assignment_expression_after);
+
+    return njs_parser_after(parser, current, NULL, 1,
+                            njs_parser_expression_comma);
+}
+
+
 static njs_int_t
 njs_parser_iteration_statement_for_map(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
@@ -5543,13 +5731,44 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser,
             return ret;
         }
 
-        break;
+        goto expression_after;
 
-    default:
+    case NJS_TOKEN_AWAIT:
         njs_parser_next(parser, njs_parser_expression);
-        break;
+
+        goto expression_after;
+
+    default:
+        ret = njs_parser_match_arrow_expression(parser, token);
+        if (ret == NJS_OK) {
+            parser->target = NULL;
+            njs_parser_next(parser, njs_parser_expression);
+            goto expression_after;
+        } else if (ret == NJS_ERROR) {
+            return NJS_ERROR;
+        }
+
+        parser->target = NULL;
+        njs_parser_next(parser, njs_parser_left_hand_side_expression);
+
+        /*
+         * Here we pass not a node, but a token, this is important.
+         * This is necessary for correct error output.
+         */
+
+        text = njs_mp_alloc(parser->vm->mem_pool, sizeof(njs_str_t));
+        if (text == NULL) {
+            return NJS_ERROR;
+        }
+
+        *text = token->text;
+
+        return njs_parser_after(parser, current, text, 0,
+                                njs_parser_for_left_hand_side_expression_map);
     }
 
+expression_after:
+
     /*
      * Here we pass not a node, but a token, this is important.
      * This is necessary for correct error output.
@@ -5617,6 +5836,8 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
             if (next->type != NJS_TOKEN_IN) {
                 parser->var_type = type;
 
+                njs_lexer_in_fail_set(parser->lexer, 1);
+
                 njs_parser_next(parser, njs_parser_variable_declaration_list);
                 return NJS_OK;
             }
@@ -5726,10 +5947,16 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser,
      * "of" <AssignmentExpression> ")" <Statement>
      */
 
-    if (parser->node->token_type == NJS_TOKEN_IN) {
+    if (token->type != NJS_TOKEN_SEMICOLON &&
+        token->type != NJS_TOKEN_CLOSE_PARENTHESIS &&
+        parser->node != NULL && parser->node->token_type == NJS_TOKEN_IN)
+    {
         node = parser->node->left;
 
-        if (node->token_type != NJS_TOKEN_NAME) {
+        if (node->token_type != NJS_TOKEN_NAME &&
+            node->token_type != NJS_TOKEN_PROPERTY)
+        {
+
             text = (njs_str_t *) parser->target;
 
             njs_parser_ref_error(parser, "Invalid left-hand side \"%V\" "
@@ -5752,6 +5979,8 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser,
 
     switch (token->type) {
     case NJS_TOKEN_SEMICOLON:
+        njs_lexer_in_fail_set(parser->lexer, 0);
+
         token = njs_lexer_peek_token(parser->lexer, token, 0);
         if (token == NULL) {
             return NJS_ERROR;
@@ -5819,6 +6048,36 @@ njs_parser_for_in_statement(njs_parser_t *parser, njs_lexer_token_t *token,
 }
 
 
+static njs_int_t
+njs_parser_for_in_statement_statement(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    njs_parser_node_t  *forin;
+
+    if (token->type != NJS_TOKEN_CLOSE_PARENTHESIS) {
+        return njs_parser_failed(parser);
+    }
+
+    njs_lexer_consume_token(parser->lexer, 1);
+
+    parser->target->right = parser->node;
+
+    forin = njs_parser_node_new(parser, NJS_TOKEN_FOR_IN);
+    if (forin == NULL) {
+        return NJS_ERROR;
+    }
+
+    forin->left = parser->target;
+
+    parser->node = NULL;
+
+    njs_parser_next(parser, njs_parser_statement_wo_node);
+
+    return njs_parser_after(parser, current, forin, 1,
+                            njs_parser_for_in_statement_after);
+}
+
+
 static njs_int_t
 njs_parser_for_in_statement_after(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
@@ -8263,6 +8522,12 @@ njs_parser_template_string(njs_parser_t *parser, njs_lexer_token_t *token)
             if (p < lexer->end && *p == '{') {
                 p++;
                 text->length = p - text->start - 2;
+
+                ret = njs_lexer_in_stack_push(lexer);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return NJS_ERROR;
+                }
+
                 goto done;
             }
 
index 9a8e9521f8ce112a1aa3370b08b88493f3afa42f..2aa9b4fc09271326c58934522511c6016f0e11d5 100644 (file)
@@ -81,6 +81,8 @@ struct njs_parser_s {
     njs_int_t                       ret;
     uintptr_t                       undefined_id;
 
+    uint8_t                         use_lhs;
+
     uint8_t                         module;
     njs_bool_t                      strict_semicolon;
 
index f875da5bd53265a2b0d34de8151455c72d96b8b2..bd1c1832f96f4e10636d1b6663c40ea9f20e9ade 100644 (file)
@@ -2900,6 +2900,68 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var a = []; for (var k in new Uint8Array([1,2,3])) { a.push(k); }; a"),
       njs_str("0,1,2") },
 
+    { njs_str("var i=0, a=[], r=[], d=[3,5];"
+              "function ret_a() {r.push('ret_a'); return a};"
+              "function ret_d() {r.push('ret_d'); return d};"
+              "for (ret_a()[i++] in 0 || ret_d()) {d[2]=22; r.push(a)}; r"),
+      njs_str("ret_d,ret_a,0,1,ret_a,0,1") },
+
+    { njs_str("this.a = 0; for (a in {b:1}) {}; a;"),
+      njs_str("b") },
+
+    { njs_str("for (var x = x in [1,2]; ; ) {break};"),
+      njs_str("SyntaxError: Invalid left-hand side in for-loop in 1") },
+
+    { njs_str("for (x = x in [1,2]; ; ) {break};"),
+      njs_str("SyntaxError: Invalid left-hand side in for-loop in 1") },
+
+    { njs_str("for (var x = (x in [1,2]); ; ) {break}; x;"),
+      njs_str("false") },
+
+    { njs_str("var x; for (x = (x in [1,2]); ; ) {break}; x;"),
+      njs_str("false") },
+
+    { njs_str("for (++a in {}; ; ) {break}"),
+      njs_str("SyntaxError: Invalid left-hand side in for-loop in 1") },
+
+    { njs_str("var a, b, c, d = 1; for (a + b, c = d; ; ){break}; c"),
+      njs_str("1") },
+
+    { njs_str("var x = 1, y, z = 'a', u = {a:1};"
+              "for (var a = x, y = z in u; ; ) {break}; y"),
+      njs_str("SyntaxError: Invalid left-hand side in for-loop in 1") },
+
+    { njs_str("var x = 1, y, z = 'a', u = {a:1};"
+              "for (var a = x, y= (z in u) ; ; ) {break}; y"),
+      njs_str("true") },
+
+    { njs_str("var a = 0; for (++a; ; ) {break}; a"),
+      njs_str("1") },
+
+    { njs_str("var a = 0; for (a++; ; ) {break}; a"),
+      njs_str("1") },
+
+    { njs_str("var a = 0; for (+a; ; ) {break}; a"),
+      njs_str("0") },
+
+    { njs_str("for (in + j;;) {}"),
+      njs_str("SyntaxError: Unexpected token \"in\" in 1") },
+
+    { njs_str("for (true ? 0 in {}: 0; false; ) ;"),
+      njs_str("undefined") },
+
+    { njs_str("for (true ? 0 : 0 in {}; false; ) ;"),
+      njs_str("SyntaxError: Invalid left-hand side in for-loop in 1") },
+
+    { njs_str("for ((a in b)) {}"),
+      njs_str("SyntaxError: Unexpected token \")\" in 1") },
+
+    { njs_str("var a='a', b={b:1}; for ((a in b); ; ) {break}; a"),
+      njs_str("a") },
+
+    { njs_str("for ((a,b,c) => {};;) {break}"),
+      njs_str("undefined") },
+
     /* switch. */
 
     { njs_str("switch"),