Skip to content

Commit 2f6c069

Browse files
authored
Implement phpGH-18261: Allow cast to be used in constant expressions (php#18264)
1 parent e11a47f commit 2f6c069

9 files changed

+182
-230
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ PHP NEWS
4444
(timwolla, Volker Dusch)
4545
. Added get_error_handler(), get_exception_handler() functions. (Arnaud)
4646
. Fixed bug GH-15753 and GH-16198 (Bind traits before parent class). (ilutov)
47+
. Added support for casts in constant expressions. (nielsdos)
4748

4849
- Curl:
4950
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ PHP 8.5 UPGRADE NOTES
130130
RFC: https://wiki.php.net/rfc/marking_return_value_as_important
131131
. Added asymmetric visibility support for static properties.
132132
RFC: https://wiki.php.net/rfc/static-aviz
133+
. Added support for casts in constant expressions.
133134

134135
- Curl:
135136
. Added support for share handles that are persisted across multiple PHP
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
Constant expressions with cast
3+
--FILE--
4+
<?php
5+
class X {
6+
public int $foo = 3;
7+
}
8+
9+
const T1 = (int) 0.3;
10+
const T2 = (bool) 0.3;
11+
const T3 = (string) [];
12+
const T4 = (object) ["a" => 1];
13+
const T5 = (float) 5;
14+
const T6 = (array) "";
15+
const T7 = (array) var_dump(...);
16+
const T8 = (array) new X;
17+
const T9 = (array) new DateTime;
18+
const T10 = (int) new DateTime;
19+
20+
var_dump(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
21+
?>
22+
--EXPECTF--
23+
Warning: Array to string conversion in %s on line %d
24+
25+
Warning: Object of class DateTime could not be converted to int in %s on line %d
26+
int(0)
27+
bool(true)
28+
string(5) "Array"
29+
object(stdClass)#%d (1) {
30+
["a"]=>
31+
int(1)
32+
}
33+
float(5)
34+
array(1) {
35+
[0]=>
36+
string(0) ""
37+
}
38+
array(1) {
39+
[0]=>
40+
object(Closure)#%d (2) {
41+
["function"]=>
42+
string(8) "var_dump"
43+
["parameter"]=>
44+
array(2) {
45+
["$value"]=>
46+
string(10) "<required>"
47+
["$values"]=>
48+
string(10) "<optional>"
49+
}
50+
}
51+
}
52+
array(1) {
53+
["foo"]=>
54+
int(3)
55+
}
56+
array(3) {
57+
["date"]=>
58+
string(%d) "%s"
59+
["timezone_type"]=>
60+
int(%d)
61+
["timezone"]=>
62+
string(%d) "%s"
63+
}
64+
int(1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Constant expressions with object cast in property
3+
--FILE--
4+
<?php
5+
class X {
6+
public $foo = (object) [];
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Object casts are not supported in this context in %s on line %d

Zend/zend_ast.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,41 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
702702
}
703703
zval_ptr_dtor_nogc(&op1);
704704
break;
705+
case ZEND_AST_CAST:
706+
if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
707+
ret = FAILURE;
708+
break;
709+
}
710+
if (ast->attr == Z_TYPE(op1)) {
711+
ZVAL_COPY_VALUE(result, &op1);
712+
} else {
713+
switch (ast->attr) {
714+
case _IS_BOOL:
715+
ZVAL_BOOL(result, zend_is_true(&op1));
716+
break;
717+
case IS_LONG:
718+
ZVAL_LONG(result, zval_get_long_func(&op1, false));
719+
break;
720+
case IS_DOUBLE:
721+
ZVAL_DOUBLE(result, zval_get_double_func(&op1));
722+
break;
723+
case IS_STRING:
724+
ZVAL_STR(result, zval_get_string_func(&op1));
725+
break;
726+
case IS_ARRAY:
727+
zend_cast_zval_to_array(result, &op1, IS_VAR);
728+
break;
729+
case IS_OBJECT:
730+
zend_cast_zval_to_object(result, &op1, IS_VAR);
731+
break;
732+
EMPTY_SWITCH_DEFAULT_CASE();
733+
}
734+
zval_ptr_dtor_nogc(&op1);
735+
if (UNEXPECTED(EG(exception))) {
736+
ret = FAILURE;
737+
}
738+
}
739+
break;
705740
case ZEND_AST_OR:
706741
if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
707742
ret = FAILURE;

Zend/zend_compile.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11121,6 +11121,7 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
1112111121
|| kind == ZEND_AST_AND || kind == ZEND_AST_OR
1112211122
|| kind == ZEND_AST_UNARY_OP
1112311123
|| kind == ZEND_AST_UNARY_PLUS || kind == ZEND_AST_UNARY_MINUS
11124+
|| kind == ZEND_AST_CAST
1112411125
|| kind == ZEND_AST_CONDITIONAL || kind == ZEND_AST_DIM
1112511126
|| kind == ZEND_AST_ARRAY || kind == ZEND_AST_ARRAY_ELEM
1112611127
|| kind == ZEND_AST_UNPACK
@@ -11395,6 +11396,12 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */
1139511396
case ZEND_AST_MAGIC_CONST:
1139611397
zend_compile_const_expr_magic_const(ast_ptr);
1139711398
break;
11399+
case ZEND_AST_CAST:
11400+
if (ast->attr == IS_OBJECT && !ctx->allow_dynamic) {
11401+
zend_error_noreturn(E_COMPILE_ERROR,
11402+
"Object casts are not supported in this context");
11403+
}
11404+
break;
1139811405
case ZEND_AST_NEW:
1139911406
if (!ctx->allow_dynamic) {
1140011407
zend_error_noreturn(E_COMPILE_ERROR,

Zend/zend_execute.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,60 @@ static zend_always_inline void zend_safe_assign_to_variable_noref(zval *variable
219219
}
220220
}
221221

222+
static zend_always_inline void zend_cast_zval_to_object(zval *result, zval *expr, uint8_t op1_type) {
223+
HashTable *ht;
224+
225+
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));
226+
if (Z_TYPE_P(expr) == IS_ARRAY) {
227+
ht = zend_symtable_to_proptable(Z_ARR_P(expr));
228+
if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) {
229+
/* TODO: try not to duplicate immutable arrays as well ??? */
230+
ht = zend_array_dup(ht);
231+
}
232+
Z_OBJ_P(result)->properties = ht;
233+
} else if (Z_TYPE_P(expr) != IS_NULL) {
234+
Z_OBJ_P(result)->properties = ht = zend_new_array(1);
235+
expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr);
236+
if (op1_type == IS_CONST) {
237+
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr);
238+
} else {
239+
if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr);
240+
}
241+
}
242+
}
243+
244+
static zend_always_inline void zend_cast_zval_to_array(zval *result, zval *expr, uint8_t op1_type) {
245+
extern zend_class_entry *zend_ce_closure;
246+
if (op1_type == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) {
247+
if (Z_TYPE_P(expr) != IS_NULL) {
248+
ZVAL_ARR(result, zend_new_array(1));
249+
expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr);
250+
if (op1_type == IS_CONST) {
251+
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr);
252+
} else {
253+
if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr);
254+
}
255+
} else {
256+
ZVAL_EMPTY_ARRAY(result);
257+
}
258+
} else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) {
259+
/* Optimized version without rebuilding properties HashTable */
260+
ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr)));
261+
} else {
262+
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
263+
if (obj_ht) {
264+
/* fast copy */
265+
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
266+
(Z_OBJCE_P(expr)->default_properties_count ||
267+
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
268+
GC_IS_RECURSIVE(obj_ht))));
269+
zend_release_properties(obj_ht);
270+
} else {
271+
ZVAL_EMPTY_ARRAY(result);
272+
}
273+
}
274+
}
275+
222276
ZEND_API zend_result ZEND_FASTCALL zval_update_constant(zval *pp);
223277
ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *pp, zend_class_entry *scope);
224278
ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *pp, zend_class_entry *scope, zend_ast_evaluate_ctx *ctx);

Zend/zend_vm_def.h

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6459,7 +6459,6 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE)
64596459
USE_OPLINE
64606460
zval *expr;
64616461
zval *result = EX_VAR(opline->result.var);
6462-
HashTable *ht;
64636462

64646463
SAVE_OPLINE();
64656464
expr = GET_OP1_ZVAL_PTR(BP_VAR_R);
@@ -6493,53 +6492,10 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE)
64936492
}
64946493

64956494
if (opline->extended_value == IS_ARRAY) {
6496-
if (OP1_TYPE == IS_CONST || Z_TYPE_P(expr) != IS_OBJECT || Z_OBJCE_P(expr) == zend_ce_closure) {
6497-
if (Z_TYPE_P(expr) != IS_NULL) {
6498-
ZVAL_ARR(result, zend_new_array(1));
6499-
expr = zend_hash_index_add_new(Z_ARRVAL_P(result), 0, expr);
6500-
if (OP1_TYPE == IS_CONST) {
6501-
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr);
6502-
} else {
6503-
if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr);
6504-
}
6505-
} else {
6506-
ZVAL_EMPTY_ARRAY(result);
6507-
}
6508-
} else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) {
6509-
/* Optimized version without rebuilding properties HashTable */
6510-
ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr)));
6511-
} else {
6512-
HashTable *obj_ht = zend_get_properties_for(expr, ZEND_PROP_PURPOSE_ARRAY_CAST);
6513-
if (obj_ht) {
6514-
/* fast copy */
6515-
ZVAL_ARR(result, zend_proptable_to_symtable(obj_ht,
6516-
(Z_OBJCE_P(expr)->default_properties_count ||
6517-
Z_OBJ_P(expr)->handlers != &std_object_handlers ||
6518-
GC_IS_RECURSIVE(obj_ht))));
6519-
zend_release_properties(obj_ht);
6520-
} else {
6521-
ZVAL_EMPTY_ARRAY(result);
6522-
}
6523-
}
6495+
zend_cast_zval_to_array(result, expr, OP1_TYPE);
65246496
} else {
65256497
ZEND_ASSERT(opline->extended_value == IS_OBJECT);
6526-
ZVAL_OBJ(result, zend_objects_new(zend_standard_class_def));
6527-
if (Z_TYPE_P(expr) == IS_ARRAY) {
6528-
ht = zend_symtable_to_proptable(Z_ARR_P(expr));
6529-
if (GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) {
6530-
/* TODO: try not to duplicate immutable arrays as well ??? */
6531-
ht = zend_array_dup(ht);
6532-
}
6533-
Z_OBJ_P(result)->properties = ht;
6534-
} else if (Z_TYPE_P(expr) != IS_NULL) {
6535-
Z_OBJ_P(result)->properties = ht = zend_new_array(1);
6536-
expr = zend_hash_add_new(ht, ZSTR_KNOWN(ZEND_STR_SCALAR), expr);
6537-
if (OP1_TYPE == IS_CONST) {
6538-
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(expr))) Z_ADDREF_P(expr);
6539-
} else {
6540-
if (Z_OPT_REFCOUNTED_P(expr)) Z_ADDREF_P(expr);
6541-
}
6542-
}
6498+
zend_cast_zval_to_object(result, expr, OP1_TYPE);
65436499
}
65446500
}
65456501

0 commit comments

Comments
 (0)