Skip to content

Commit 153e057

Browse files
committed
Cleanup
1 parent 19193f2 commit 153e057

6 files changed

Lines changed: 119 additions & 241 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Promoted readonly property reassignment through plain function should error
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public function __construct(public readonly int $prop) {
8+
set($this);
9+
}
10+
11+
public function set() {
12+
$this->prop++;
13+
}
14+
}
15+
16+
function set($c) {
17+
try {
18+
$c->set();
19+
} catch (Error $e) {
20+
echo $e::class, ': ', $e->getMessage(), "\n";
21+
}
22+
}
23+
24+
$c = new C(42);
25+
var_dump($c->prop);
26+
27+
?>
28+
--EXPECT--
29+
Error: Cannot modify readonly property C::$prop
30+
int(42)

Zend/zend_execute.c

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,22 +1071,9 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in
10711071
static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC)
10721072
{
10731073
zval tmp;
1074-
zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN;
1075-
1076-
if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
1077-
if (info->flags & ZEND_ACC_READONLY) {
1078-
readonly_write_kind = zend_get_readonly_write_kind(property_val, info);
1079-
}
1080-
if ((info->flags & ZEND_ACC_READONLY)
1081-
&& (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN
1082-
|| zend_is_foreign_cpp_overwrite(property_val, info))) {
1083-
zend_readonly_property_modification_error(info);
1084-
return &EG(uninitialized_zval);
1085-
}
1086-
if (info->flags & ZEND_ACC_PPP_SET_MASK && !zend_asymmetric_property_has_set_access(info)) {
1087-
zend_asymmetric_visibility_property_modification_error(info, "modify");
1088-
return &EG(uninitialized_zval);
1089-
}
1074+
zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(property_val, info, false);
1075+
if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) {
1076+
return &EG(uninitialized_zval);
10901077
}
10911078

10921079
ZVAL_DEREF(value);
@@ -1097,11 +1084,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf
10971084
return &EG(uninitialized_zval);
10981085
}
10991086

1100-
if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) {
1101-
Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE;
1102-
} else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) {
1103-
Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED;
1104-
}
1087+
zend_property_write_commit(property_val, prop_write_kind);
11051088

11061089
return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr);
11071090
}

Zend/zend_execute.h

Lines changed: 53 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -595,132 +595,87 @@ static zend_always_inline bool zend_scope_is_derived_from(
595595
return false;
596596
}
597597

598-
static zend_always_inline bool zend_has_active_derived_ctor_with_promoted_property(
599-
const zend_execute_data *ex, zend_object *obj, const zend_property_info *prop_info)
600-
{
601-
for (const zend_execute_data *prev = ex->prev_execute_data; prev != NULL; prev = prev->prev_execute_data) {
602-
if (!(ZEND_CALL_INFO(prev) & ZEND_CALL_HAS_THIS)
603-
|| !(prev->func->common.fn_flags & ZEND_ACC_CTOR)
604-
|| Z_OBJ(prev->This) != obj) {
605-
continue;
606-
}
607-
608-
zend_class_entry *scope = prev->func->common.scope;
609-
if (scope == NULL || !zend_scope_is_derived_from(scope, ex->func->common.scope)) {
610-
continue;
611-
}
612-
613-
zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(
614-
&scope->properties_info, prop_info->name);
615-
if (scope_prop != NULL
616-
&& (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED))
617-
== (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) {
618-
return true;
619-
}
620-
}
621-
622-
return false;
623-
}
624-
625598
static zend_always_inline bool zend_has_active_ctor_with_promoted_property(
626599
const zend_execute_data *ex, zend_object *obj, zend_string *property_name)
627600
{
628601
for (const zend_execute_data *frame = ex; frame != NULL; frame = frame->prev_execute_data) {
629-
if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS)
630-
|| !(frame->func->common.fn_flags & ZEND_ACC_CTOR)
631-
|| Z_OBJ(frame->This) != obj) {
602+
if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) || Z_OBJ(frame->This) != obj) {
603+
return false;
604+
}
605+
if (!(frame->func->common.fn_flags & ZEND_ACC_CTOR)) {
632606
continue;
633607
}
634608

635609
zend_class_entry *scope = frame->func->common.scope;
636-
if (scope == NULL) {
637-
continue;
638-
}
610+
ZEND_ASSERT(scope);
639611

640-
zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(
641-
&scope->properties_info, property_name);
642-
if (scope_prop != NULL
643-
&& (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED))
644-
== (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) {
645-
return true;
646-
}
612+
zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(&scope->properties_info, property_name);
613+
ZEND_ASSERT(scope_prop);
614+
return scope_prop->ce == scope && (scope_prop->flags & (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED);
647615
}
648616

649617
return false;
650618
}
651619

652-
static zend_always_inline bool zend_is_promoted_readonly_ctor_init_assign(
653-
const zend_execute_data *ex, const zend_property_info *prop_info)
654-
{
655-
if (ex == NULL
656-
|| ex->opline == NULL
657-
|| !((ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF)
658-
&& (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) {
659-
return false;
660-
}
661-
if (!(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) {
662-
return false;
663-
}
664-
return zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name);
665-
}
666-
667-
static zend_always_inline zend_object *zend_property_owner_from_slot(
668-
const zval *property_val, const zend_property_info *prop_info)
669-
{
670-
return (zend_object *) ((char *) property_val - prop_info->offset);
671-
}
672-
673-
typedef enum _zend_readonly_write_kind {
674-
ZEND_READONLY_WRITE_FORBIDDEN = 0, /* Write disallowed, or no special flag update needed */
675-
ZEND_READONLY_WRITE_REINITABLE, /* Clone reinit window: clear IS_PROP_REINITABLE after write */
676-
ZEND_READONLY_WRITE_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */
677-
} zend_readonly_write_kind;
620+
typedef enum _zend_property_write_kind {
621+
ZEND_PROPERTY_WRITE_FORBIDDEN = 0, /* Write disallowed */
622+
ZEND_PROPERTY_WRITE_OK, /* Write allowed */
623+
ZEND_PROPERTY_WRITE_READONLY_REINITABLE, /* Clone reinit window: clear IS_PROP_REINITABLE after write */
624+
ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */
625+
} zend_property_write_kind;
678626

679-
static zend_always_inline zend_readonly_write_kind zend_get_readonly_write_kind(
627+
static zend_always_inline zend_property_write_kind zend_get_readonly_write_kind(
680628
const zval *property_val, const zend_property_info *prop_info)
681629
{
682630
if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) {
683-
return ZEND_READONLY_WRITE_REINITABLE;
631+
return ZEND_PROPERTY_WRITE_READONLY_REINITABLE;
684632
}
685633
if (Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REASSIGNED) {
686-
return ZEND_READONLY_WRITE_FORBIDDEN;
634+
return ZEND_PROPERTY_WRITE_FORBIDDEN;
687635
}
688636
zend_execute_data *ex = EG(current_execute_data);
689-
if (ex == NULL
690-
|| !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)
691-
|| !zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name)
692-
|| zend_property_owner_from_slot(property_val, prop_info) != Z_OBJ(ex->This)
693-
|| (ex->opline != NULL
694-
&& (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF)
695-
&& (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) {
696-
return ZEND_READONLY_WRITE_FORBIDDEN;
637+
if (ex == NULL || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) {
638+
return ZEND_PROPERTY_WRITE_FORBIDDEN;
697639
}
698-
return ZEND_READONLY_WRITE_CTOR_REASSIGNED;
640+
zend_object *obj = zend_get_object_from_slot(property_val, prop_info);
641+
if (!zend_has_active_ctor_with_promoted_property(ex, obj, prop_info->name)
642+
|| (ex->opline
643+
&& (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF)
644+
&& (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) {
645+
return ZEND_PROPERTY_WRITE_FORBIDDEN;
646+
}
647+
return ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED;
699648
}
700649

701-
/* Check if a foreign constructor is attempting a CPP initial assignment on an
702-
* already-initialized property owned by a different class (e.g., child has CPP
703-
* for $x, parent's CPP also tries to set $x on the child's object). */
704-
static zend_always_inline bool zend_is_foreign_cpp_overwrite(
705-
const zval *property_val, const zend_property_info *prop_info)
650+
static zend_always_inline zend_property_write_kind zend_verify_readonly_and_avis(
651+
zval *property_val, const zend_property_info *info, bool indirect)
706652
{
707-
if (Z_PROP_FLAG_P(property_val) & IS_PROP_UNINIT) {
708-
return false;
709-
}
710-
zend_execute_data *ex = EG(current_execute_data);
711-
if (!ex
712-
|| !(ex->func->common.fn_flags & ZEND_ACC_CTOR)
713-
|| !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) {
714-
return false;
715-
}
716-
if ((prop_info->flags & ZEND_ACC_PROMOTED) && ex->func->common.scope != prop_info->ce) {
717-
return true;
653+
if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
654+
zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK;
655+
if ((info->flags & ZEND_ACC_READONLY) && !Z_ISUNDEF_P(property_val)) {
656+
prop_write_kind = zend_get_readonly_write_kind(property_val, info);
657+
if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) {
658+
zend_readonly_property_modification_error(info);
659+
return ZEND_PROPERTY_WRITE_FORBIDDEN;
660+
}
661+
}
662+
if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) {
663+
const char *operation = indirect ? "indirectly modify" : "modify";
664+
zend_asymmetric_visibility_property_modification_error(info, operation);
665+
return ZEND_PROPERTY_WRITE_FORBIDDEN;
666+
}
667+
return prop_write_kind;
718668
}
719-
if (!(prop_info->flags & ZEND_ACC_PROMOTED)
720-
&& zend_has_active_derived_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info)) {
721-
return true;
669+
return ZEND_PROPERTY_WRITE_OK;
670+
}
671+
672+
static zend_always_inline void zend_property_write_commit(zval *property_val, zend_property_write_kind kind)
673+
{
674+
if (kind == ZEND_PROPERTY_WRITE_READONLY_REINITABLE) {
675+
Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE;
676+
} else if (kind == ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED) {
677+
Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED;
722678
}
723-
return false;
724679
}
725680

726681
ZEND_API bool zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, zval *constant);

Zend/zend_object_handlers.c

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,14 +1049,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
10491049
uintptr_t property_offset;
10501050
const zend_property_info *prop_info = NULL;
10511051
uint32_t *guard = NULL;
1052-
zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN;
10531052
ZEND_ASSERT(!Z_ISREF_P(value));
10541053

10551054
property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info);
10561055

10571056
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
1058-
try_again:
1059-
readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN;
1057+
try_again:;
1058+
zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK;
10601059
variable_ptr = OBJ_PROP(zobj, property_offset);
10611060

10621061
if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
@@ -1068,19 +1067,8 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
10681067
error = (*guard) & IN_SET;
10691068
}
10701069
if (error) {
1071-
if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF) {
1072-
readonly_write_kind = zend_get_readonly_write_kind(variable_ptr, prop_info);
1073-
}
1074-
if ((prop_info->flags & ZEND_ACC_READONLY)
1075-
&& Z_TYPE_P(variable_ptr) != IS_UNDEF
1076-
&& (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN
1077-
|| zend_is_foreign_cpp_overwrite(variable_ptr, prop_info))) {
1078-
zend_readonly_property_modification_error(prop_info);
1079-
variable_ptr = &EG(error_zval);
1080-
goto exit;
1081-
}
1082-
if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) {
1083-
zend_asymmetric_visibility_property_modification_error(prop_info, "modify");
1070+
prop_write_kind = zend_verify_readonly_and_avis(variable_ptr, prop_info, false);
1071+
if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) {
10841072
variable_ptr = &EG(error_zval);
10851073
goto exit;
10861074
}
@@ -1118,11 +1106,7 @@ found:;
11181106
variable_ptr = zend_assign_to_variable_ex(
11191107
variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage);
11201108

1121-
if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) {
1122-
Z_PROP_FLAG_P(variable_ptr) &= ~IS_PROP_REINITABLE;
1123-
} else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) {
1124-
Z_PROP_FLAG_P(variable_ptr) |= IS_PROP_CTOR_REASSIGNED;
1125-
}
1109+
zend_property_write_commit(variable_ptr, prop_write_kind);
11261110

11271111
if (garbage) {
11281112
if (GC_DELREF(garbage) == 0) {

Zend/zend_objects_API.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ static inline zend_property_info *zend_get_typed_property_info_for_slot(zend_obj
137137
return NULL;
138138
}
139139

140+
static zend_always_inline zend_object *zend_get_object_from_slot(const zval *slot, const zend_property_info *prop_info)
141+
{
142+
return (zend_object *)((char *)slot - prop_info->offset);
143+
}
144+
140145
static zend_always_inline bool zend_check_method_accessible(const zend_function *fn, const zend_class_entry *scope)
141146
{
142147
if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC)

0 commit comments

Comments
 (0)