From 3c616f5173ba23f56b512c65b60fccd8031eff0b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 6 Apr 2025 13:54:36 +0200 Subject: [PATCH 1/7] Implement GH-18261: Allow cast to be used in constant expressions --- UPGRADING | 1 + .../constexpr/constant_expressions_cast.phpt | 51 +++++++++++++++ Zend/zend_ast.c | 65 +++++++++++++++++++ Zend/zend_compile.c | 1 + 4 files changed, 118 insertions(+) create mode 100644 Zend/tests/constexpr/constant_expressions_cast.phpt diff --git a/UPGRADING b/UPGRADING index 26ed02c9af9a0..b4fc1b2284e04 100644 --- a/UPGRADING +++ b/UPGRADING @@ -130,6 +130,7 @@ PHP 8.5 UPGRADE NOTES RFC: https://wiki.php.net/rfc/marking_return_value_as_important . Added asymmetric visibility support for static properties. RFC: https://wiki.php.net/rfc/static-aviz + . It is now possible to use casts in constant expressions. - Curl: . Added support for share handles that are persisted across multiple PHP diff --git a/Zend/tests/constexpr/constant_expressions_cast.phpt b/Zend/tests/constexpr/constant_expressions_cast.phpt new file mode 100644 index 0000000000000..367cb48028858 --- /dev/null +++ b/Zend/tests/constexpr/constant_expressions_cast.phpt @@ -0,0 +1,51 @@ +--TEST-- +Constant expressions with cast +--FILE-- + 1]; +const T5 = (float) 5; +const T6 = (array) ""; +const T7 = (array) var_dump(...); +const T8 = (array) new X; + +var_dump(T1, T2, T3, T4, T5, T6, T7, T8); +?> +--EXPECTF-- +Warning: Array to string conversion in %s on line %d +int(0) +bool(true) +string(5) "Array" +object(stdClass)#%d (1) { + ["a"]=> + int(1) +} +float(5) +array(1) { + [0]=> + string(0) "" +} +array(1) { + [0]=> + object(Closure)#%d (2) { + ["function"]=> + string(8) "var_dump" + ["parameter"]=> + array(2) { + ["$value"]=> + string(10) "" + ["$values"]=> + string(10) "" + } + } +} +array(1) { + ["foo"]=> + int(3) +} diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 2551f876d4465..fd20544165cf9 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -702,6 +702,71 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( } zval_ptr_dtor_nogc(&op1); break; + case ZEND_AST_CAST: + if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) { + ret = FAILURE; + break; + } + if (ast->attr == Z_TYPE(op1)) { + ZVAL_COPY_VALUE(result, &op1); + } else { + switch (ast->attr) { + case _IS_BOOL: + ZVAL_BOOL(result, zend_is_true(&op1)); + zval_ptr_dtor_nogc(&op1); + break; + case IS_LONG: + ZVAL_LONG(result, zval_get_long_func(&op1, false)); + zval_ptr_dtor_nogc(&op1); + break; + case IS_DOUBLE: + ZVAL_DOUBLE(result, zval_get_double_func(&op1)); + zval_ptr_dtor_nogc(&op1); + break; + case IS_STRING: + ZVAL_STR(result, zval_get_string_func(&op1)); + zval_ptr_dtor_nogc(&op1); + break; + case IS_ARRAY: + /* Adapted from VM */ + if (Z_TYPE(op1) != IS_OBJECT || Z_OBJCE(op1) == zend_ce_closure) { + if (Z_TYPE(op1) != IS_NULL) { + ZVAL_ARR(result, zend_new_array(1)); + zend_hash_index_add_new(Z_ARRVAL_P(result), 0, &op1); + } else { + ZVAL_EMPTY_ARRAY(result); + } + } else { + /* Constant AST only allows building stdClass objects, closures, or user classes; + * all of which should be compatible. */ + ZEND_ASSERT(ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(&op1)); + + /* Optimized version without rebuilding properties HashTable */ + ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ(op1))); + zval_ptr_dtor_nogc(&op1); + } + break; + case IS_OBJECT: + /* Adapted from VM */ + ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); + if (Z_TYPE(op1) == IS_ARRAY) { + HashTable *ht = zend_symtable_to_proptable(Z_ARR(op1)); + if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { + /* TODO: try not to duplicate immutable arrays as well ??? */ + ht = zend_array_dup(ht); + } + Z_OBJ_P(result)->properties = ht; + zval_ptr_dtor_nogc(&op1); + } else if (Z_TYPE(op1) != IS_NULL) { + HashTable *ht = zend_new_array(1); + Z_OBJ_P(result)->properties = ht; + zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), &op1); + } + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + } + break; case ZEND_AST_OR: if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) { ret = FAILURE; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2e405de30ea62..42c227f107dfc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11156,6 +11156,7 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_AND || kind == ZEND_AST_OR || kind == ZEND_AST_UNARY_OP || kind == ZEND_AST_UNARY_PLUS || kind == ZEND_AST_UNARY_MINUS + || kind == ZEND_AST_CAST || kind == ZEND_AST_CONDITIONAL || kind == ZEND_AST_DIM || kind == ZEND_AST_ARRAY || kind == ZEND_AST_ARRAY_ELEM || kind == ZEND_AST_UNPACK From d81b1b87f2733ad7e1ef1972ddd23ab4f9e95f86 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:41:22 +0200 Subject: [PATCH 2/7] review --- .../constexpr/constant_expressions_cast.phpt | 15 ++++++++++++++- ...tant_expressions_cast_object_property.phpt | 10 ++++++++++ Zend/zend_ast.c | 19 +++++++++++++------ Zend/zend_compile.c | 6 ++++++ 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/constexpr/constant_expressions_cast_object_property.phpt diff --git a/Zend/tests/constexpr/constant_expressions_cast.phpt b/Zend/tests/constexpr/constant_expressions_cast.phpt index 367cb48028858..4431e2c08d6b0 100644 --- a/Zend/tests/constexpr/constant_expressions_cast.phpt +++ b/Zend/tests/constexpr/constant_expressions_cast.phpt @@ -14,11 +14,15 @@ const T5 = (float) 5; const T6 = (array) ""; const T7 = (array) var_dump(...); const T8 = (array) new X; +const T9 = (array) new DateTime; +const T10 = (int) new DateTime; -var_dump(T1, T2, T3, T4, T5, T6, T7, T8); +var_dump(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); ?> --EXPECTF-- Warning: Array to string conversion in %s on line %d + +Warning: Object of class DateTime could not be converted to int in %s on line %d int(0) bool(true) string(5) "Array" @@ -49,3 +53,12 @@ array(1) { ["foo"]=> int(3) } +array(3) { + ["date"]=> + string(%d) "%s" + ["timezone_type"]=> + int(%d) + ["timezone"]=> + string(%d) "%s" +} +int(1) diff --git a/Zend/tests/constexpr/constant_expressions_cast_object_property.phpt b/Zend/tests/constexpr/constant_expressions_cast_object_property.phpt new file mode 100644 index 0000000000000..63616f0f8bf3b --- /dev/null +++ b/Zend/tests/constexpr/constant_expressions_cast_object_property.phpt @@ -0,0 +1,10 @@ +--TEST-- +Constant expressions with object cast in property +--FILE-- + +--EXPECTF-- +Fatal error: Object casts are not supported in this context in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index fd20544165cf9..62232333690cd 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -736,14 +736,21 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( } else { ZVAL_EMPTY_ARRAY(result); } - } else { - /* Constant AST only allows building stdClass objects, closures, or user classes; - * all of which should be compatible. */ - ZEND_ASSERT(ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(&op1)); - - /* Optimized version without rebuilding properties HashTable */ + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(&op1)) { ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ(op1))); zval_ptr_dtor_nogc(&op1); + } else { + HashTable *obj_ht = zend_get_properties_for(&op1, ZEND_PROP_PURPOSE_ARRAY_CAST); + if (obj_ht) { + ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, + (Z_OBJCE(op1)->default_properties_count || + Z_OBJ(op1)->handlers != &std_object_handlers || + GC_IS_RECURSIVE(obj_ht)))); + zend_release_properties(obj_ht); + } else { + ZVAL_EMPTY_ARRAY(result); + } + zval_ptr_dtor_nogc(&op1); } break; case IS_OBJECT: diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 42c227f107dfc..a82af1580c9a7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11431,6 +11431,12 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */ case ZEND_AST_MAGIC_CONST: zend_compile_const_expr_magic_const(ast_ptr); break; + case ZEND_AST_CAST: + if (ast->attr == IS_OBJECT && !ctx->allow_dynamic) { + zend_error_noreturn(E_COMPILE_ERROR, + "Object casts are not supported in this context"); + } + break; case ZEND_AST_NEW: if (!ctx->allow_dynamic) { zend_error_noreturn(E_COMPILE_ERROR, From d26405788eec62dea5408afa89a40525ceed8814 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:38:53 +0200 Subject: [PATCH 3/7] Factor out object cast --- Zend/zend_ast.c | 17 ++-------- Zend/zend_execute.h | 22 ++++++++++++ Zend/zend_vm_def.h | 19 +---------- Zend/zend_vm_execute.h | 76 +++--------------------------------------- 4 files changed, 29 insertions(+), 105 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 62232333690cd..7b4673b16e329 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -754,21 +754,8 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( } break; case IS_OBJECT: - /* Adapted from VM */ - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE(op1) == IS_ARRAY) { - HashTable *ht = zend_symtable_to_proptable(Z_ARR(op1)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - zval_ptr_dtor_nogc(&op1); - } else if (Z_TYPE(op1) != IS_NULL) { - HashTable *ht = zend_new_array(1); - Z_OBJ_P(result)->properties = ht; - zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), &op1); - } + zend_cast_zval_to_object(result, &op1, IS_VAR); + Z_TRY_DELREF(op1); break; EMPTY_SWITCH_DEFAULT_CASE(); } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index e96a217a2904f..e4a3651c63245 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -219,6 +219,28 @@ static zend_always_inline void zend_safe_assign_to_variable_noref(zval *variable } } +static zend_always_inline void zend_cast_zval_to_object(zval *result, zval *expr, uint8_t op1_type) { + HashTable *ht; + + ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); + if (Z_TYPE_P(expr) == IS_ARRAY) { + ht = zend_symtable_to_proptable(Z_ARR_P(expr)); + if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { + /* TODO: try not to duplicate immutable arrays as well ??? */ + ht = zend_array_dup(ht); + } + Z_OBJ_P(result)->properties = ht; + } else if (Z_TYPE_P(expr) != IS_NULL) { + Z_OBJ_P(result)->properties = ht = zend_new_array(1); + expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); + if (op1_type == IS_CONST) { + if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); + } else { + if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); + } + } +} + ZEND_API zend_result ZEND_FASTCALL zval_update_constant(zval *pp); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *pp, zend_class_entry *scope); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *pp, zend_class_entry *scope, zend_ast_evaluate_ctx *ctx); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 162b7e84cefbe..1a7b6221a08ec 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6460,7 +6460,6 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE) USE_OPLINE zval *expr; zval *result = EX_VAR(opline->result.var); - HashTable *ht; SAVE_OPLINE(); expr = GET_OP1_ZVAL_PTR(BP_VAR_R); @@ -6524,23 +6523,7 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE) } } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE_P(expr) == IS_ARRAY) { - ht = zend_symtable_to_proptable(Z_ARR_P(expr)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - } else if (Z_TYPE_P(expr) != IS_NULL) { - Z_OBJ_P(result)->properties = ht = zend_new_array(1); - expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); - if (OP1_TYPE == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } + zend_cast_zval_to_object(result, expr, OP1_TYPE); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 902e254ff2424..ebdb064d53fea 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5233,7 +5233,6 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H USE_OPLINE zval *expr; zval *result = EX_VAR(opline->result.var); - HashTable *ht; SAVE_OPLINE(); expr = RT_CONSTANT(opline, opline->op1); @@ -5296,23 +5295,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H } } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE_P(expr) == IS_ARRAY) { - ht = zend_symtable_to_proptable(Z_ARR_P(expr)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - } else if (Z_TYPE_P(expr) != IS_NULL) { - Z_OBJ_P(result)->properties = ht = zend_new_array(1); - expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); - if (IS_CONST == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } + zend_cast_zval_to_object(result, expr, IS_CONST); } } @@ -20155,7 +20138,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC USE_OPLINE zval *expr; zval *result = EX_VAR(opline->result.var); - HashTable *ht; SAVE_OPLINE(); expr = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC); @@ -20218,23 +20200,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC } } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE_P(expr) == IS_ARRAY) { - ht = zend_symtable_to_proptable(Z_ARR_P(expr)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - } else if (Z_TYPE_P(expr) != IS_NULL) { - Z_OBJ_P(result)->properties = ht = zend_new_array(1); - expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); - if (IS_TMP_VAR == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } + zend_cast_zval_to_object(result, expr, IS_TMP_VAR); } } @@ -22822,7 +22788,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC USE_OPLINE zval *expr; zval *result = EX_VAR(opline->result.var); - HashTable *ht; SAVE_OPLINE(); expr = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -22886,23 +22851,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC } } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE_P(expr) == IS_ARRAY) { - ht = zend_symtable_to_proptable(Z_ARR_P(expr)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - } else if (Z_TYPE_P(expr) != IS_NULL) { - Z_OBJ_P(result)->properties = ht = zend_new_array(1); - expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); - if (IS_VAR == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } + zend_cast_zval_to_object(result, expr, IS_VAR); } } @@ -41065,7 +41014,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO USE_OPLINE zval *expr; zval *result = EX_VAR(opline->result.var); - HashTable *ht; SAVE_OPLINE(); expr = _get_zval_ptr_cv_BP_VAR_R(opline->op1.var EXECUTE_DATA_CC); @@ -41128,23 +41076,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO } } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); - ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def)); - if (Z_TYPE_P(expr) == IS_ARRAY) { - ht = zend_symtable_to_proptable(Z_ARR_P(expr)); - if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) { - /* TODO: try not to duplicate immutable arrays as well ??? */ - ht = zend_array_dup(ht); - } - Z_OBJ_P(result)->properties = ht; - } else if (Z_TYPE_P(expr) != IS_NULL) { - Z_OBJ_P(result)->properties = ht = zend_new_array(1); - expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr); - if (IS_CV == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } + zend_cast_zval_to_object(result, expr, IS_CV); } } From d08c58426b16c125415d03a936806e7c099bc2b8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:45:36 +0200 Subject: [PATCH 4/7] Factor out array cast --- Zend/zend_ast.c | 31 +---------- Zend/zend_execute.h | 32 ++++++++++++ Zend/zend_vm_def.h | 29 +---------- Zend/zend_vm_execute.h | 116 ++--------------------------------------- 4 files changed, 39 insertions(+), 169 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7b4673b16e329..76d003ad421d7 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -713,52 +713,25 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( switch (ast->attr) { case _IS_BOOL: ZVAL_BOOL(result, zend_is_true(&op1)); - zval_ptr_dtor_nogc(&op1); break; case IS_LONG: ZVAL_LONG(result, zval_get_long_func(&op1, false)); - zval_ptr_dtor_nogc(&op1); break; case IS_DOUBLE: ZVAL_DOUBLE(result, zval_get_double_func(&op1)); - zval_ptr_dtor_nogc(&op1); break; case IS_STRING: ZVAL_STR(result, zval_get_string_func(&op1)); - zval_ptr_dtor_nogc(&op1); break; case IS_ARRAY: - /* Adapted from VM */ - if (Z_TYPE(op1) != IS_OBJECT || Z_OBJCE(op1) == zend_ce_closure) { - if (Z_TYPE(op1) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - zend_hash_index_add_new(Z_ARRVAL_P(result), 0, &op1); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(&op1)) { - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ(op1))); - zval_ptr_dtor_nogc(&op1); - } else { - HashTable *obj_ht = zend_get_properties_for(&op1, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE(op1)->default_properties_count || - Z_OBJ(op1)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - zval_ptr_dtor_nogc(&op1); - } + zend_cast_zval_to_array(result, &op1, IS_VAR); break; case IS_OBJECT: zend_cast_zval_to_object(result, &op1, IS_VAR); - Z_TRY_DELREF(op1); break; EMPTY_SWITCH_DEFAULT_CASE(); } + zval_ptr_dtor_nogc(&op1); } break; case ZEND_AST_OR: diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index e4a3651c63245..7f9b41e465502 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -241,6 +241,38 @@ static zend_always_inline void zend_cast_zval_to_object(zval *result, zval *expr } } +static zend_always_inline void zend_cast_zval_to_array(zval *result, zval *expr, uint8_t op1_type) { + extern zend_class_entry *zend_ce_closure; + if (op1_type == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { + if (Z_TYPE_P(expr) != IS_NULL) { + ZVAL_ARR(result, zend_new_array(1)); + expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); + if (op1_type == IS_CONST) { + if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); + } else { + if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); + } + } else { + ZVAL_EMPTY_ARRAY(result); + } + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { + /* Optimized version without rebuilding properties HashTable */ + ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); + } else { + HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); + if (obj_ht) { + /* fast copy */ + ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, + (Z_OBJCE_P(expr)->default_properties_count || + Z_OBJ_P(expr)->handlers != &std_object_handlers || + GC_IS_RECURSIVE(obj_ht)))); + zend_release_properties(obj_ht); + } else { + ZVAL_EMPTY_ARRAY(result); + } + } +} + ZEND_API zend_result ZEND_FASTCALL zval_update_constant(zval *pp); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *pp, zend_class_entry *scope); ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *pp, zend_class_entry *scope, zend_ast_evaluate_ctx *ctx); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1a7b6221a08ec..3e1eead81706c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6493,34 +6493,7 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE) } if (opline->extended_value == IS_ARRAY) { - if (OP1_TYPE == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { - if (Z_TYPE_P(expr) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); - if (OP1_TYPE == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { - /* Optimized version without rebuilding properties HashTable */ - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); - } else { - HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - /* fast copy */ - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } + zend_cast_zval_to_array(result, expr, OP1_TYPE); } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); zend_cast_zval_to_object(result, expr, OP1_TYPE); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index ebdb064d53fea..26f423cdbe011 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5265,34 +5265,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H } if (opline->extended_value == IS_ARRAY) { - if (IS_CONST == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { - if (Z_TYPE_P(expr) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); - if (IS_CONST == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { - /* Optimized version without rebuilding properties HashTable */ - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); - } else { - HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - /* fast copy */ - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } + zend_cast_zval_to_array(result, expr, IS_CONST); } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); zend_cast_zval_to_object(result, expr, IS_CONST); @@ -20170,34 +20143,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC } if (opline->extended_value == IS_ARRAY) { - if (IS_TMP_VAR == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { - if (Z_TYPE_P(expr) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); - if (IS_TMP_VAR == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { - /* Optimized version without rebuilding properties HashTable */ - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); - } else { - HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - /* fast copy */ - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } + zend_cast_zval_to_array(result, expr, IS_TMP_VAR); } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); zend_cast_zval_to_object(result, expr, IS_TMP_VAR); @@ -22821,34 +22767,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC } if (opline->extended_value == IS_ARRAY) { - if (IS_VAR == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { - if (Z_TYPE_P(expr) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); - if (IS_VAR == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { - /* Optimized version without rebuilding properties HashTable */ - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); - } else { - HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - /* fast copy */ - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } + zend_cast_zval_to_array(result, expr, IS_VAR); } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); zend_cast_zval_to_object(result, expr, IS_VAR); @@ -41046,34 +40965,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO } if (opline->extended_value == IS_ARRAY) { - if (IS_CV == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) { - if (Z_TYPE_P(expr) != IS_NULL) { - ZVAL_ARR(result, zend_new_array(1)); - expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr); - if (IS_CV == IS_CONST) { - if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr); - } else { - if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr); - } - } else { - ZVAL_EMPTY_ARRAY(result); - } - } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { - /* Optimized version without rebuilding properties HashTable */ - ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); - } else { - HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST); - if (obj_ht) { - /* fast copy */ - ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht, - (Z_OBJCE_P(expr)->default_properties_count || - Z_OBJ_P(expr)->handlers != &std_object_handlers || - GC_IS_RECURSIVE(obj_ht)))); - zend_release_properties(obj_ht); - } else { - ZVAL_EMPTY_ARRAY(result); - } - } + zend_cast_zval_to_array(result, expr, IS_CV); } else { ZEND_ASSERT(opline->extended_value == IS_OBJECT); zend_cast_zval_to_object(result, expr, IS_CV); From 40d8e9b3a6101c43aea858c0621b0692028a8ca8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:51:21 +0200 Subject: [PATCH 5/7] exception check --- Zend/zend_ast.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 76d003ad421d7..991d85ff39ce1 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -732,6 +732,9 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( EMPTY_SWITCH_DEFAULT_CASE(); } zval_ptr_dtor_nogc(&op1); + if (UNEXPECTED(EG(exception))) { + ret = FAILURE; + } } break; case ZEND_AST_OR: From 93b514597100d7c2cbc924f705e950b277a1c6cd Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:35:39 +0200 Subject: [PATCH 6/7] [ci skip] Better wording in UPGRADING --- UPGRADING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING b/UPGRADING index b4fc1b2284e04..c7d0dc0e80c4b 100644 --- a/UPGRADING +++ b/UPGRADING @@ -130,7 +130,7 @@ PHP 8.5 UPGRADE NOTES RFC: https://wiki.php.net/rfc/marking_return_value_as_important . Added asymmetric visibility support for static properties. RFC: https://wiki.php.net/rfc/static-aviz - . It is now possible to use casts in constant expressions. + . Added support for casts in constant expressions. - Curl: . Added support for share handles that are persisted across multiple PHP From 3a3ba873b759def388ed913bbc93e49abbd4b5be Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:36:06 +0200 Subject: [PATCH 7/7] [ci skip] Add NEWS --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 65cebd217e130..4872d1294c677 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,7 @@ PHP NEWS (timwolla, Volker Dusch) . Added get_error_handler(), get_exception_handler() functions. (Arnaud) . Fixed bug GH-15753 and GH-16198 (Bind traits before parent class). (ilutov) + . Added support for casts in constant expressions. (nielsdos) - Curl: . Added curl_multi_get_handles(). (timwolla)