@@ -1049,12 +1049,14 @@ 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 ;
10521053 ZEND_ASSERT (!Z_ISREF_P (value ));
10531054
10541055 property_offset = zend_get_property_offset (zobj -> ce , name , (zobj -> ce -> __set != NULL ), cache_slot , & prop_info );
10551056
10561057 if (EXPECTED (IS_VALID_PROPERTY_OFFSET (property_offset ))) {
10571058try_again :
1059+ readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN ;
10581060 variable_ptr = OBJ_PROP (zobj , property_offset );
10591061
10601062 if (prop_info && UNEXPECTED (prop_info -> flags & (ZEND_ACC_READONLY |ZEND_ACC_PPP_SET_MASK ))) {
@@ -1066,9 +1068,12 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
10661068 error = (* guard ) & IN_SET ;
10671069 }
10681070 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+ }
10691074 if ((prop_info -> flags & ZEND_ACC_READONLY )
10701075 && Z_TYPE_P (variable_ptr ) != IS_UNDEF
1071- && (! zend_readonly_property_is_reinitable_for_context ( variable_ptr , prop_info )
1076+ && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN
10721077 || zend_is_foreign_cpp_overwrite (variable_ptr , prop_info ))) {
10731078 zend_readonly_property_modification_error (prop_info );
10741079 variable_ptr = & EG (error_zval );
@@ -1103,42 +1108,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
11031108 variable_ptr = & EG (error_zval );
11041109 goto exit ;
11051110 }
1106- /* For readonly properties initialized for the first time via CPP, set
1107- * IS_PROP_REINITABLE to allow one reassignment in the constructor body.
1108- * The flag is cleared by zend_leave_helper when the constructor exits.
1109- *
1110- * Classical case: the property is promoted in the declaring class and the
1111- * executing constructor belongs to that class (scope == prop_info->ce).
1112- *
1113- * Extended case: a child class redeclared the property without CPP, so
1114- * prop_info->ce is the child but the property isn't promoted there. CPP
1115- * "ownership" still belongs to the ancestor whose constructor has CPP for
1116- * this property name, so its body is allowed to reassign once. The clearing
1117- * loop in zend_leave_helper iterates the exiting ctor's own promoted props,
1118- * which share the same object slot, so cleanup happens automatically. */
1119- bool reinitable = false;
1120- if ((prop_info -> flags & ZEND_ACC_READONLY )
1121- && (Z_PROP_FLAG_P (variable_ptr ) & IS_PROP_UNINIT )
1122- && EG (current_execute_data )
1123- && (EG (current_execute_data )-> func -> common .fn_flags & ZEND_ACC_CTOR )) {
1124- zend_class_entry * ctor_scope = EG (current_execute_data )-> func -> common .scope ;
1125- if (prop_info -> flags & ZEND_ACC_PROMOTED ) {
1126- reinitable = (ctor_scope == prop_info -> ce );
1127- } else if (ctor_scope != prop_info -> ce ) {
1128- /* Child redeclared without CPP: check if the executing ctor's class
1129- * has a CPP declaration for this property name. */
1130- zend_property_info * scope_prop = zend_hash_find_ptr (
1131- & ctor_scope -> properties_info , prop_info -> name );
1132- reinitable = scope_prop != NULL
1133- && (scope_prop -> flags & (ZEND_ACC_READONLY |ZEND_ACC_PROMOTED ))
1134- == (ZEND_ACC_READONLY |ZEND_ACC_PROMOTED );
1135- }
1136- }
1137- if (reinitable ) {
1138- Z_PROP_FLAG_P (variable_ptr ) = IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE ;
1139- } else {
1140- Z_PROP_FLAG_P (variable_ptr ) &= ~(IS_PROP_UNINIT |IS_PROP_REINITABLE |IS_PROP_CTOR_REINITABLE );
1141- }
1111+ Z_PROP_FLAG_P (variable_ptr ) &= ~(IS_PROP_UNINIT |IS_PROP_REINITABLE );
11421112 value = & tmp ;
11431113 }
11441114
@@ -1148,6 +1118,12 @@ found:;
11481118 variable_ptr = zend_assign_to_variable_ex (
11491119 variable_ptr , value , IS_TMP_VAR , property_uses_strict_types (), & garbage );
11501120
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+ }
1126+
11511127 if (garbage ) {
11521128 if (GC_DELREF (garbage ) == 0 ) {
11531129 zend_execute_data * execute_data = EG (current_execute_data );
@@ -1556,7 +1532,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
15561532 if (error ) {
15571533 if ((prop_info -> flags & ZEND_ACC_READONLY )
15581534 && Z_TYPE_P (slot ) != IS_UNDEF
1559- && !zend_readonly_property_is_reinitable_for_context ( slot , prop_info )) {
1535+ && !( Z_PROP_FLAG_P ( slot ) & IS_PROP_REINITABLE )) {
15601536 zend_readonly_property_unset_error (prop_info -> ce , name );
15611537 return ;
15621538 }
0 commit comments