From ec3afe8db638a5b4966efb784276b87d6edbefa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 11 Apr 2025 09:54:09 +0200 Subject: [PATCH 1/2] =?UTF-8?q?zend=5Fcompile:=20Allow=20`(void)`=20in=20f?= =?UTF-8?q?or=E2=80=99s=20initializer=20and=20loop=20expression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initializer and loop expression of a `for()` loop only accept expressions, but they act like statements in spirit - their results are completely ignored. Allow `(void)` there to allow suppressing `#[\NoDiscard]`, since there is no semantic ambiguity of what `(void)` returns anyways. Fixes php/php-src#18301 --- .../type_casts/gh18301_cast_to_void_for.phpt | 46 +++++++++++++++ ..._cast_to_void_statement_for_condition.phpt | 9 +++ Zend/zend_compile.c | 56 ++++++++++--------- Zend/zend_language_parser.y | 20 +++++-- 4 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 Zend/tests/type_casts/gh18301_cast_to_void_for.phpt create mode 100644 Zend/tests/type_casts/gh18301_cast_to_void_statement_for_condition.phpt diff --git a/Zend/tests/type_casts/gh18301_cast_to_void_for.phpt b/Zend/tests/type_casts/gh18301_cast_to_void_for.phpt new file mode 100644 index 000000000000..2ff7cfb0cb17 --- /dev/null +++ b/Zend/tests/type_casts/gh18301_cast_to_void_for.phpt @@ -0,0 +1,46 @@ +--TEST-- +GH-18301: casting to void is allowed in for’s expression lists +--FILE-- + +--EXPECTF-- +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d +4 + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d +10 + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d +16 + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d +22 + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d +28 + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d + +Warning: The return value of function incCount() should either be used or intentionally ignored by casting it as (void) in %s on line %d diff --git a/Zend/tests/type_casts/gh18301_cast_to_void_statement_for_condition.phpt b/Zend/tests/type_casts/gh18301_cast_to_void_statement_for_condition.phpt new file mode 100644 index 000000000000..6c90b2f7987c --- /dev/null +++ b/Zend/tests/type_casts/gh18301_cast_to_void_statement_for_condition.phpt @@ -0,0 +1,9 @@ +--TEST-- +GH-18301: casting to void is not allowed at the end of a for condition +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token ";", expecting "," in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2ad3f6b323d8..5414d686d982 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5717,6 +5717,26 @@ static void zend_compile_return(zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_void_cast(znode *result, zend_ast *ast) +{ + zend_ast *expr_ast = ast->child[0]; + znode expr_node; + zend_op *opline; + + zend_compile_expr(&expr_node, expr_ast); + + switch (expr_node.op_type) { + case IS_TMP_VAR: + case IS_VAR: + opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL); + opline->extended_value = ZEND_FREE_VOID_CAST; + break; + case IS_CONST: + zend_do_free(&expr_node); + break; + } +} + static void zend_compile_echo(zend_ast *ast) /* {{{ */ { zend_op *opline; @@ -5967,7 +5987,7 @@ static void zend_compile_do_while(zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_compile_expr_list(znode *result, zend_ast *ast) /* {{{ */ +static void zend_compile_for_expr_list(znode *result, zend_ast *ast) /* {{{ */ { zend_ast_list *list; uint32_t i; @@ -5984,7 +6004,13 @@ static void zend_compile_expr_list(znode *result, zend_ast *ast) /* {{{ */ zend_ast *expr_ast = list->child[i]; zend_do_free(result); - zend_compile_expr(result, expr_ast); + if (expr_ast->kind == ZEND_AST_CAST_VOID) { + zend_compile_void_cast(NULL, expr_ast); + result->op_type = IS_CONST; + ZVAL_NULL(&result->u.constant); + } else { + zend_compile_expr(result, expr_ast); + } } } /* }}} */ @@ -5999,7 +6025,7 @@ static void zend_compile_for(zend_ast *ast) /* {{{ */ znode result; uint32_t opnum_start, opnum_jmp, opnum_loop; - zend_compile_expr_list(&result, init_ast); + zend_compile_for_expr_list(&result, init_ast); zend_do_free(&result); opnum_jmp = zend_emit_jump(0); @@ -6010,11 +6036,11 @@ static void zend_compile_for(zend_ast *ast) /* {{{ */ zend_compile_stmt(stmt_ast); opnum_loop = get_next_op_number(); - zend_compile_expr_list(&result, loop_ast); + zend_compile_for_expr_list(&result, loop_ast); zend_do_free(&result); zend_update_jump_target_to_next(opnum_jmp); - zend_compile_expr_list(&result, cond_ast); + zend_compile_for_expr_list(&result, cond_ast); zend_do_extended_stmt(); zend_emit_cond_jump(ZEND_JMPNZ, &result, opnum_start); @@ -10594,26 +10620,6 @@ static void zend_compile_include_or_eval(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_compile_void_cast(znode *result, zend_ast *ast) -{ - zend_ast *expr_ast = ast->child[0]; - znode expr_node; - zend_op *opline; - - zend_compile_expr(&expr_node, expr_ast); - - switch (expr_node.op_type) { - case IS_TMP_VAR: - case IS_VAR: - opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL); - opline->extended_value = ZEND_FREE_VOID_CAST; - break; - case IS_CONST: - zend_do_free(&expr_node); - break; - } -} - static void zend_compile_isset_or_empty(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *var_ast = ast->child[0]; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9483a83b4e95..67727dae8cd5 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -268,11 +268,11 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type callable_expr callable_variable static_member new_variable %type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt -%type alt_if_stmt for_exprs switch_case_list global_var_list static_var_list +%type alt_if_stmt for_exprs for_expr_statements switch_case_list global_var_list static_var_list %type echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list %type implements_list case_list if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list -%type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs +%type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs non_empty_for_expr_statements %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars %type lexical_var_list encaps_list %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair @@ -508,7 +508,7 @@ statement: { $$ = zend_ast_create(ZEND_AST_WHILE, $3, $5); } | T_DO statement T_WHILE '(' expr ')' ';' { $$ = zend_ast_create(ZEND_AST_DO_WHILE, $2, $5); } - | T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement + | T_FOR '(' for_expr_statements ';' for_exprs ';' for_expr_statements ')' for_statement { $$ = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); } | T_SWITCH '(' expr ')' switch_case_list { $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $5); } @@ -1175,7 +1175,19 @@ for_exprs: ; non_empty_for_exprs: - non_empty_for_exprs ',' expr { $$ = zend_ast_list_add($1, $3); } + non_empty_for_expr_statements ',' expr { $$ = zend_ast_list_add($1, $3); } + | expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, $1); } +; + +for_expr_statements: + %empty { $$ = NULL; } + | non_empty_for_expr_statements { $$ = $1; } +; + +non_empty_for_expr_statements: + non_empty_for_expr_statements ',' expr { $$ = zend_ast_list_add($1, $3); } + | non_empty_for_expr_statements ',' T_VOID_CAST expr { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CAST_VOID, $4)); } + | T_VOID_CAST expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, zend_ast_create(ZEND_AST_CAST_VOID, $2)); } | expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, $1); } ; From 7386028a3b15ab53cb579774c269a2f60268bd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 11 Apr 2025 10:18:06 +0200 Subject: [PATCH 2/2] zend_language_parser: Simplify `for()` grammar --- Zend/zend_language_parser.y | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 67727dae8cd5..190292fada76 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -268,11 +268,11 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type callable_expr callable_variable static_member new_variable %type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt -%type alt_if_stmt for_exprs for_expr_statements switch_case_list global_var_list static_var_list +%type alt_if_stmt for_cond_exprs for_exprs switch_case_list global_var_list static_var_list %type echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list %type implements_list case_list if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list -%type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs non_empty_for_expr_statements +%type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars %type lexical_var_list encaps_list %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair @@ -508,7 +508,7 @@ statement: { $$ = zend_ast_create(ZEND_AST_WHILE, $3, $5); } | T_DO statement T_WHILE '(' expr ')' ';' { $$ = zend_ast_create(ZEND_AST_DO_WHILE, $2, $5); } - | T_FOR '(' for_expr_statements ';' for_exprs ';' for_expr_statements ')' for_statement + | T_FOR '(' for_exprs ';' for_cond_exprs ';' for_exprs ')' for_statement { $$ = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); } | T_SWITCH '(' expr ')' switch_case_list { $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $5); } @@ -1169,24 +1169,20 @@ echo_expr: expr { $$ = zend_ast_create(ZEND_AST_ECHO, $1); } ; -for_exprs: +for_cond_exprs: %empty { $$ = NULL; } - | non_empty_for_exprs { $$ = $1; } -; - -non_empty_for_exprs: - non_empty_for_expr_statements ',' expr { $$ = zend_ast_list_add($1, $3); } + | non_empty_for_exprs ',' expr { $$ = zend_ast_list_add($1, $3); } | expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, $1); } ; -for_expr_statements: +for_exprs: %empty { $$ = NULL; } - | non_empty_for_expr_statements { $$ = $1; } + | non_empty_for_exprs { $$ = $1; } ; -non_empty_for_expr_statements: - non_empty_for_expr_statements ',' expr { $$ = zend_ast_list_add($1, $3); } - | non_empty_for_expr_statements ',' T_VOID_CAST expr { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CAST_VOID, $4)); } +non_empty_for_exprs: + non_empty_for_exprs ',' expr { $$ = zend_ast_list_add($1, $3); } + | non_empty_for_exprs ',' T_VOID_CAST expr { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CAST_VOID, $4)); } | T_VOID_CAST expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, zend_ast_create(ZEND_AST_CAST_VOID, $2)); } | expr { $$ = zend_ast_create_list(1, ZEND_AST_EXPR_LIST, $1); } ;