From e6ff592cf3878649980877818ae536ffa7b2f06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 18 Mar 2025 11:07:20 +0100 Subject: [PATCH 1/2] Add `(void)` cast RFC: https://wiki.php.net/rfc/marking_return_value_as_important --- Zend/Optimizer/block_pass.c | 4 +- Zend/Optimizer/dce.c | 3 +- Zend/tests/type_casts/cast_to_void.phpt | 47 +++++++++++++++++++ Zend/tests/type_casts/cast_to_void_ast.phpt | 18 +++++++ .../type_casts/cast_to_void_destructor.phpt | 30 ++++++++++++ .../type_casts/cast_to_void_statement.phpt | 11 +++++ Zend/zend_ast.c | 3 ++ Zend/zend_ast.h | 1 + Zend/zend_compile.c | 23 +++++++++ Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 2 + Zend/zend_language_scanner.l | 4 ++ ext/tokenizer/tokenizer_data.c | 1 + ext/tokenizer/tokenizer_data.stub.php | 5 ++ ext/tokenizer/tokenizer_data_arginfo.h | 3 +- 15 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/type_casts/cast_to_void.phpt create mode 100644 Zend/tests/type_casts/cast_to_void_ast.phpt create mode 100644 Zend/tests/type_casts/cast_to_void_destructor.phpt create mode 100644 Zend/tests/type_casts/cast_to_void_statement.phpt diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 6fcbd04f12af5..2b6d71c385457 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -274,7 +274,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array * If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary, * hence removing the temporary is not safe in the general case, especially when other consumers are not FREE. * A FREE may not be removed without also removing the source's result, because otherwise that would cause a memory leak. */ - if (opline->op1_type == IS_TMP_VAR) { + if (opline->extended_value == ZEND_FREE_VOID_CAST) { + /* Keep the ZEND_FREE opcode alive. */ + } else if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src) { switch (src->opcode) { diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c index 414abe01f96ac..a00fd8bc6ad30 100644 --- a/Zend/Optimizer/dce.c +++ b/Zend/Optimizer/dce.c @@ -80,7 +80,6 @@ static inline bool may_have_side_effects( case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_QM_ASSIGN: - case ZEND_FREE: case ZEND_FE_FREE: case ZEND_TYPE_CHECK: case ZEND_DEFINED: @@ -127,6 +126,8 @@ static inline bool may_have_side_effects( case ZEND_ARRAY_KEY_EXISTS: /* No side effects */ return 0; + case ZEND_FREE: + return opline->extended_value == ZEND_FREE_VOID_CAST; case ZEND_ADD_ARRAY_ELEMENT: /* TODO: We can't free two vars. Keep instruction alive. "$b"]; */ if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) { diff --git a/Zend/tests/type_casts/cast_to_void.phpt b/Zend/tests/type_casts/cast_to_void.phpt new file mode 100644 index 0000000000000..cdbcffad519c9 --- /dev/null +++ b/Zend/tests/type_casts/cast_to_void.phpt @@ -0,0 +1,47 @@ +--TEST-- +casting different variables to void +--FILE-- + +--EXPECTF-- +Done diff --git a/Zend/tests/type_casts/cast_to_void_ast.phpt b/Zend/tests/type_casts/cast_to_void_ast.phpt new file mode 100644 index 0000000000000..26911bddb7ebc --- /dev/null +++ b/Zend/tests/type_casts/cast_to_void_ast.phpt @@ -0,0 +1,18 @@ +--TEST-- +(void) is included in AST printing +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +assert(false && function () { + (void)somefunc(); +}) diff --git a/Zend/tests/type_casts/cast_to_void_destructor.phpt b/Zend/tests/type_casts/cast_to_void_destructor.phpt new file mode 100644 index 0000000000000..027e4b77e5dda --- /dev/null +++ b/Zend/tests/type_casts/cast_to_void_destructor.phpt @@ -0,0 +1,30 @@ +--TEST-- +casting to void destroys the value. +--FILE-- + +--EXPECT-- +Before +WithDestructor::__destruct +After diff --git a/Zend/tests/type_casts/cast_to_void_statement.phpt b/Zend/tests/type_casts/cast_to_void_statement.phpt new file mode 100644 index 0000000000000..3262e1efd549c --- /dev/null +++ b/Zend/tests/type_casts/cast_to_void_statement.phpt @@ -0,0 +1,11 @@ +--TEST-- +casting to void is a statement +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "(void)" in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8b0a66fda8096..1ae7a0ef7a68e 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2261,6 +2261,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio EMPTY_SWITCH_DEFAULT_CASE(); } break; + case ZEND_AST_CAST_VOID: + PREFIX_OP("(void)", 240, 241); + break; case ZEND_AST_EMPTY: FUNC_OP("empty"); case ZEND_AST_ISSET: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index d0dad8490c4e3..9348c35f6cc07 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -84,6 +84,7 @@ enum _zend_ast_kind { ZEND_AST_UNARY_PLUS, ZEND_AST_UNARY_MINUS, ZEND_AST_CAST, + ZEND_AST_CAST_VOID, ZEND_AST_EMPTY, ZEND_AST_ISSET, ZEND_AST_SILENCE, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index cd0ac6eb07b9c..1cba9ef221997 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -10589,6 +10589,26 @@ 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]; @@ -11547,6 +11567,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_THROW: zend_compile_expr(NULL, ast); break; + case ZEND_AST_CAST_VOID: + zend_compile_void_cast(NULL, ast); + break; default: { znode result; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index a7ee8f9327c54..224a68be749cb 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1092,6 +1092,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FREE_ON_RETURN (1<<0) #define ZEND_FREE_SWITCH (1<<1) +#define ZEND_FREE_VOID_CAST (1<<2) #define ZEND_SEND_BY_VAL 0u #define ZEND_SEND_BY_REF 1u diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 1f117d142c158..9483a83b4e955 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -217,6 +217,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_OBJECT_CAST "'(object)'" %token T_BOOL_CAST "'(bool)'" %token T_UNSET_CAST "'(unset)'" +%token T_VOID_CAST "'(void)'" %token T_OBJECT_OPERATOR "'->'" %token T_NULLSAFE_OBJECT_OPERATOR "'?->'" %token T_DOUBLE_ARROW "'=>'" @@ -534,6 +535,7 @@ statement: { $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); } | T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); } | T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); } + | T_VOID_CAST expr ';' { $$ = zend_ast_create(ZEND_AST_CAST_VOID, $2); } ; catch_list: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 7ae73875926eb..4c883b81c5f7d 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1657,6 +1657,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN(T_UNSET_CAST); } +"("{TABS_AND_SPACES}("void"){TABS_AND_SPACES}")" { + RETURN_TOKEN(T_VOID_CAST); +} + "eval" { RETURN_TOKEN_WITH_IDENT(T_EVAL); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a046ab50e1498..a1e131032bcfb 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -153,6 +153,7 @@ char *get_token_type_name(int token_type) case T_OBJECT_CAST: return "T_OBJECT_CAST"; case T_BOOL_CAST: return "T_BOOL_CAST"; case T_UNSET_CAST: return "T_UNSET_CAST"; + case T_VOID_CAST: return "T_VOID_CAST"; case T_OBJECT_OPERATOR: return "T_OBJECT_OPERATOR"; case T_NULLSAFE_OBJECT_OPERATOR: return "T_NULLSAFE_OBJECT_OPERATOR"; case T_DOUBLE_ARROW: return "T_DOUBLE_ARROW"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 45f3c89f2de3a..c1e1fd254dfaa 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -642,6 +642,11 @@ * @cvalue T_UNSET_CAST */ const T_UNSET_CAST = UNKNOWN; +/** + * @var int + * @cvalue T_VOID_CAST + */ +const T_VOID_CAST = UNKNOWN; /** * @var int * @cvalue T_OBJECT_OPERATOR diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 61f6ac1ec3659..9c488d19f1890 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d917cab61a2b436a16d2227cdb438add45e42d69 */ + * Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */ static void register_tokenizer_data_symbols(int module_number) { @@ -131,6 +131,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_OBJECT_CAST", T_OBJECT_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BOOL_CAST", T_BOOL_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_UNSET_CAST", T_UNSET_CAST, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_VOID_CAST", T_VOID_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_OBJECT_OPERATOR", T_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NULLSAFE_OBJECT_OPERATOR", T_NULLSAFE_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_ARROW", T_DOUBLE_ARROW, CONST_PERSISTENT); From 46243c8b62d10694a7f5df033edab8338b25d404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 24 Mar 2025 14:47:10 +0100 Subject: [PATCH 2/2] NEWS/UPGRADING --- NEWS | 2 ++ UPGRADING | 3 +++ 2 files changed, 5 insertions(+) diff --git a/NEWS b/NEWS index fcb849ef98a58..a5609377683ed 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,8 @@ PHP NEWS . Fixed bug GH-18033 (NULL-ptr dereference when using register_tick_function in destructor). (nielsdos) . Fixed bug GH-18026 (Improve "expecting token" error for ampersand). (ilutov) + . Added the (void) cast to indicate that not using a value is intentional. + (timwolla) - Curl: . Added curl_multi_get_handles(). (timwolla) diff --git a/UPGRADING b/UPGRADING index 274f45d8f401b..1f58e462e6715 100644 --- a/UPGRADING +++ b/UPGRADING @@ -106,6 +106,9 @@ PHP 8.5 UPGRADE NOTES . Fatal Errors (such as an exceeded maximum execution time) now include a backtrace. RFC: https://wiki.php.net/rfc/error_backtraces_v2 + . Added the (void) to indicate that not using a value is intentional. The + (void) cast does nothing by itself. + RFC: https://wiki.php.net/rfc/marking_return_value_as_important - Curl: . Added support for share handles that are persisted across multiple PHP