From: Dmitry Volyntsev Date: Tue, 1 Sep 2026 04:07:06 +0000 (-0700) Subject: Parser: parse the "for" initializer once X-Git-Tag: 1.0.2~87 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=6bf9c059e672480431638759755d03368ba1eb3a;p=njs.git Parser: parse the "for" initializer once Previously, the initializer was parsed twice: speculatively as a LeftHandSideExpression so that the token after it could be inspected, and then the rest of the expression was grafted onto that left-hand side by four continuation states. A dispatch list and an arrow pre-check existed only to keep inputs away from the speculative parse. The fix is to let [~In] end the initializer: the "in" operator is no longer part of the expression, so the header is a single and njs_parser_for_var_in_of_expression(), which already classified the other routes, classifies this one too. Its post-hoc for-in detection goes with it. A declaration header stopping at "in" now reports an invalid target instead of an unexpected token: for (var a, b in []); The first token's text was carried through the header only to be quoted in the removed diagnostic, so the allocation goes with it. --- diff --git a/src/njs_parser.c b/src/njs_parser.c index 010a9b4e..ccb86ea0 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -326,14 +326,6 @@ 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_expression_map_continue( - 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, @@ -347,8 +339,6 @@ static njs_int_t njs_parser_for_var_in_statement_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_var_in_of_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); -static njs_int_t njs_parser_for_in_statement(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_in_statement_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_expression(njs_parser_t *parser, @@ -4209,9 +4199,9 @@ njs_parser_relational_expression_match(njs_parser_t *parser, case NJS_TOKEN_IN: if (!parser->allow_in) { - njs_parser_syntax_error(parser, "Invalid left-hand side in for-loop"); - return NJS_ERROR; + return njs_parser_stack_pop(parser); } + operation = NJS_VMCODE_PROPERTY_IN; break; @@ -5795,162 +5785,11 @@ njs_parser_iteration_statement_for(njs_parser_t *parser, } -static njs_int_t -njs_parser_for_expression_map_continue(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 (token->type != NJS_TOKEN_IN) { - parser->allow_in = 0; - - /* 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 (!njs_parser_is_lvalue(parser->node)) { - text = (njs_str_t *) parser->target; - - njs_mp_free(parser->vm->mem_pool, text); - - njs_parser_syntax_error(parser, - "Invalid left-hand side in for-loop"); - - return NJS_ERROR; - } - - parser->node = njs_parser_lvalue_ref(parser->node); - if (parser->node == NULL) { - return NJS_ERROR; - } - - 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) -{ - return njs_parser_right_link_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) -{ - if (parser->ret != NJS_OK) { - return njs_parser_failed(parser); - } - - 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) { njs_int_t ret; - njs_str_t *text; njs_token_type_t token_type; /* @@ -6022,76 +5861,17 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser, goto expression_after; default: - ret = njs_parser_match_arrow_expression(parser, token); - if (ret == NJS_OK) { - parser->allow_in = 0; - - parser->target = NULL; - njs_parser_next(parser, njs_parser_expression); - - goto expression_after; - } else if (ret == NJS_ERROR) { - return NJS_ERROR; - } - - /* The speculative parse below has no way back, see the log. */ - - switch (token->type) { - case NJS_TOKEN_DELETE: - case NJS_TOKEN_VOID: - case NJS_TOKEN_TYPEOF: - case NJS_TOKEN_AWAIT: - case NJS_TOKEN_ADDITION: - case NJS_TOKEN_SUBTRACTION: - case NJS_TOKEN_BITWISE_NOT: - case NJS_TOKEN_LOGICAL_NOT: - case NJS_TOKEN_INCREMENT: - case NJS_TOKEN_DECREMENT: - parser->allow_in = 0; - - parser->target = NULL; - njs_parser_next(parser, njs_parser_expression); - - goto expression_after; - - default: - break; - } - + parser->allow_in = 0; 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; + njs_parser_next(parser, njs_parser_expression); - return njs_parser_after(parser, current, text, 1, - njs_parser_for_expression_map_continue); + goto expression_after; } expression_after: - /* - * 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, + return njs_parser_after(parser, current, NULL, 1, njs_parser_for_var_in_of_expression); } @@ -6245,7 +6025,6 @@ static njs_int_t njs_parser_for_var_in_of_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_str_t *text; njs_parser_node_t *node; /* @@ -6254,41 +6033,6 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser, * "of" ")" */ - 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 (!njs_parser_is_lvalue(node)) { - - 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; - } - - node = njs_parser_lvalue_ref(node); - if (node == NULL) { - return NJS_ERROR; - } - - parser->node->left = node; - - njs_parser_next(parser, njs_parser_for_in_statement); - return NJS_OK; - } - - if (parser->target != NULL) { - text = (njs_str_t *) parser->target; - - njs_mp_free(parser->vm->mem_pool, text); - } - switch (token->type) { case NJS_TOKEN_SEMICOLON: parser->allow_in = 1; @@ -6317,46 +6061,43 @@ njs_parser_for_var_in_of_expression(njs_parser_t *parser, return NJS_OK; - case NJS_TOKEN_OF: - return njs_parser_not_supported(parser, token); - - default: - return njs_parser_failed(parser); - } -} - - -static njs_int_t -njs_parser_for_in_statement(njs_parser_t *parser, njs_lexer_token_t *token, - njs_queue_link_t *current) -{ - njs_parser_node_t *node, *forin; + case NJS_TOKEN_IN: + if (!njs_parser_is_lvalue(parser->node)) { + njs_parser_syntax_error(parser, + "Invalid left-hand side in for-loop"); + return NJS_ERROR; + } - if (token->type != NJS_TOKEN_CLOSE_PARENTHESIS) { - return njs_parser_failed(parser); - } + parser->node = njs_parser_lvalue_ref(parser->node); + if (parser->node == NULL) { + return NJS_ERROR; + } - njs_lexer_consume_token(parser->lexer, 1); + node = njs_parser_node_new(parser, token->type); + if (node == NULL) { + return NJS_ERROR; + } - node = parser->node; + node->token_line = token->line; + node->u.operation = NJS_VMCODE_PROPERTY_IN; + node->left = parser->node; + node->left->dest = node; - if (node->right != NULL && node->right->token_type == NJS_TOKEN_VAR) { - return NJS_ERROR; - } + njs_lexer_consume_token(parser->lexer, 1); - forin = njs_parser_node_new(parser, NJS_TOKEN_FOR_IN); - if (forin == NULL) { - return NJS_ERROR; - } + parser->allow_in = 1; - forin->left = parser->node; + njs_parser_next(parser, njs_parser_expression); - parser->node = NULL; + return njs_parser_after(parser, current, node, 0, + njs_parser_for_in_statement_statement); - njs_parser_next(parser, njs_parser_statement_wo_node); + case NJS_TOKEN_OF: + return njs_parser_not_supported(parser, token); - return njs_parser_after(parser, current, forin, 1, - njs_parser_for_in_statement_after); + default: + return njs_parser_failed(parser); + } } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 3f7a56a6..2a801b38 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -3437,7 +3437,22 @@ static njs_unit_test_t njs_test[] = njs_str("x") }, { njs_str("for (var a, b in []);"), - njs_str("SyntaxError: Unexpected token \"in\"") }, + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + { njs_str("for (var x = 0 in o);"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, + + /* [+In] is restored past the initializer. */ + + { njs_str("var b = 'a', c = {a:1}, n = 0;" + "for (var k in b in c) { n++ } n"), + njs_str("0") }, + + { njs_str("var a = 0; for (; 1 in [1]; 1 in [1]) break; a"), + njs_str("0") }, + + { njs_str("var a = 0; for (a < 1 in {};;) break; a"), + njs_str("SyntaxError: Invalid left-hand side in for-loop") }, { njs_str("var s = ''; for (var p in [1,2]) {s += p}; s"), njs_str("01") },