diff --git a/Zend/tests/property_hooks/default_value_inheritance.phpt b/Zend/tests/property_hooks/default_value_inheritance.phpt new file mode 100644 index 0000000000000..3821e19634e76 --- /dev/null +++ b/Zend/tests/property_hooks/default_value_inheritance.phpt @@ -0,0 +1,54 @@ +--TEST-- +Property default values are not inherited +--FILE-- + parent::$a::get(); } + public int $b { get => parent::$b::get(); } + public $c = 2 { get => parent::$c::get(); } + public int $d = 2 { get => parent::$d::get(); } +} + +class GC extends C { + public $a { get => parent::$a::get(); } + public int $b { get => parent::$b::get(); } + public $c { get => parent::$c::get(); } + public int $d { get => parent::$d::get(); } +} + +function test(P $p) { + var_dump($p->a); + try { + var_dump($p->b); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + var_dump($p->c); + try { + var_dump($p->d); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +test(new C); +test(new GC); + +?> +--EXPECT-- +NULL +Typed property C::$b must not be accessed before initialization +int(2) +int(2) +NULL +Typed property GC::$b must not be accessed before initialization +NULL +Typed property GC::$d must not be accessed before initialization diff --git a/Zend/tests/property_hooks/dump.phpt b/Zend/tests/property_hooks/dump.phpt index d7cd57183d63f..84db6cb3175b0 100644 --- a/Zend/tests/property_hooks/dump.phpt +++ b/Zend/tests/property_hooks/dump.phpt @@ -34,7 +34,7 @@ class Test { } class Child extends Test { - public $addedHooks { + public $addedHooks = 'addedHooks' { get { return strtoupper(parent::$addedHooks::get()); } } private $changed = 'changed Child' { diff --git a/Zend/tests/property_hooks/generator_hook_002.phpt b/Zend/tests/property_hooks/generator_hook_002.phpt index 0eeb99c8cf9b4..5945720c340d2 100644 --- a/Zend/tests/property_hooks/generator_hook_002.phpt +++ b/Zend/tests/property_hooks/generator_hook_002.phpt @@ -8,7 +8,7 @@ class A { } class B extends A { - public $prop { + public $prop = 42 { get { yield parent::$prop::get() + 1; yield parent::$prop::get() + 2; diff --git a/Zend/tests/property_hooks/gh17376.phpt b/Zend/tests/property_hooks/gh17376.phpt new file mode 100644 index 0000000000000..d0896ff4599e4 --- /dev/null +++ b/Zend/tests/property_hooks/gh17376.phpt @@ -0,0 +1,119 @@ +--TEST-- +GH-17376: Child classes may add hooks to plain properties +--INI-- +# Avoid triggering for main, trigger for test so we get a side-trace for property hooks +opcache.jit_hot_func=2 +--FILE-- +prop; + } + set { + echo __METHOD__, "\n"; + $this->prop = $value; + } + } +} + +function test(A $a) { + echo "read\n"; + var_dump($a->prop); + echo "write\n"; + $a->prop = 42; + echo "read-write\n"; + $a->prop += 43; + echo "pre-inc\n"; + ++$a->prop; + echo "pre-dec\n"; + --$a->prop; + echo "post-inc\n"; + $a->prop++; + echo "post-dec\n"; + $a->prop--; +} + +echo "A\n"; +test(new A); + +echo "\nA\n"; +test(new A); + +echo "\nB\n"; +test(new B); + +echo "\nB\n"; +test(new B); + +?> +--EXPECT-- +A +read +int(1) +write +read-write +pre-inc +pre-dec +post-inc +post-dec + +A +read +int(1) +write +read-write +pre-inc +pre-dec +post-inc +post-dec + +B +read +B::$prop::get +int(1) +write +B::$prop::set +read-write +B::$prop::get +B::$prop::set +pre-inc +B::$prop::get +B::$prop::set +pre-dec +B::$prop::get +B::$prop::set +post-inc +B::$prop::get +B::$prop::set +post-dec +B::$prop::get +B::$prop::set + +B +read +B::$prop::get +int(1) +write +B::$prop::set +read-write +B::$prop::get +B::$prop::set +pre-inc +B::$prop::get +B::$prop::set +pre-dec +B::$prop::get +B::$prop::set +post-inc +B::$prop::get +B::$prop::set +post-dec +B::$prop::get +B::$prop::set diff --git a/Zend/tests/property_hooks/multi_level_inheritance.phpt b/Zend/tests/property_hooks/multi_level_inheritance.phpt new file mode 100644 index 0000000000000..a41a32fd71fd5 --- /dev/null +++ b/Zend/tests/property_hooks/multi_level_inheritance.phpt @@ -0,0 +1,70 @@ +--TEST-- +Property hooks with multi level inheritance +--FILE-- + parent::$prop::get() * 2; } +} + +class C extends B { + public $prop = 3; +} + +function test(A $a) { + var_dump($a); + var_dump((array)$a); + var_dump(unserialize(serialize($a))); + var_dump(get_object_vars($a)); + var_dump(json_decode(json_encode($a))); +} + +test(new B); +test(new C); + +?> +--EXPECTF-- +object(B)#%d (1) { + ["prop"]=> + int(2) +} +array(1) { + ["prop"]=> + int(2) +} +object(B)#%d (1) { + ["prop"]=> + int(2) +} +array(1) { + ["prop"]=> + int(4) +} +object(stdClass)#%d (1) { + ["prop"]=> + int(4) +} +object(C)#%d (1) { + ["prop"]=> + int(3) +} +array(1) { + ["prop"]=> + int(3) +} +object(C)#%d (1) { + ["prop"]=> + int(3) +} +array(1) { + ["prop"]=> + int(6) +} +object(stdClass)#%d (1) { + ["prop"]=> + int(6) +} diff --git a/Zend/tests/property_hooks/parent_get_plain.phpt b/Zend/tests/property_hooks/parent_get_plain.phpt index f461b39a8a372..c3dc72d51fee8 100644 --- a/Zend/tests/property_hooks/parent_get_plain.phpt +++ b/Zend/tests/property_hooks/parent_get_plain.phpt @@ -8,7 +8,7 @@ class P { } class C extends P { - public $prop { + public $prop = 42 { get => parent::$prop::get(); } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 63572ab6623cc..a7ee8f9327c54 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -463,7 +463,7 @@ typedef struct _zend_property_info { #define OBJ_PROP_TO_OFFSET(num) \ ((uint32_t)(XtOffsetOf(zend_object, properties_table) + sizeof(zval) * (num))) #define OBJ_PROP_TO_NUM(offset) \ - ((offset - OBJ_PROP_TO_OFFSET(0)) / sizeof(zval)) + (((offset) - OBJ_PROP_TO_OFFSET(0)) / sizeof(zval)) typedef struct _zend_class_constant { zval value; /* flags are stored in u2 */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 23806af57f693..f5496514fcb7d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1448,7 +1448,7 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { if (!(parent_info->ce->ce_flags & ZEND_ACC_INTERFACE)) { - child_info->prototype = parent_info; + child_info->prototype = parent_info->prototype; } if (UNEXPECTED((parent_info->flags & ZEND_ACC_STATIC) != (child_info->flags & ZEND_ACC_STATIC))) { @@ -1490,17 +1490,44 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker"); } if (!(child_info->flags & ZEND_ACC_STATIC) && !(parent_info->flags & ZEND_ACC_VIRTUAL)) { - if (child_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { - int parent_num = OBJ_PROP_TO_NUM(parent_info->offset); - int child_num = OBJ_PROP_TO_NUM(child_info->offset); + /* If we added hooks to the child property, we use the child's slot for + * storage to keep the parent slot set to IS_UNDEF. This automatically + * picks the slow path in the JIT. */ + bool use_child_prop = !parent_info->hooks && child_info->hooks; + + if (use_child_prop && child_info->offset == ZEND_VIRTUAL_PROPERTY_OFFSET) { + child_info->offset = OBJ_PROP_TO_OFFSET(ce->default_properties_count); + ce->default_properties_count++; + ce->default_properties_table = perealloc(ce->default_properties_table, sizeof(zval) * ce->default_properties_count, ce->type == ZEND_INTERNAL_CLASS); + zval *property_default_ptr = &ce->default_properties_table[OBJ_PROP_TO_NUM(child_info->offset)]; + ZVAL_UNDEF(property_default_ptr); + Z_PROP_FLAG_P(property_default_ptr) = IS_PROP_UNINIT; + } + int parent_num = OBJ_PROP_TO_NUM(parent_info->offset); + if (child_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { /* Don't keep default properties in GC (they may be freed by opcache) */ zval_ptr_dtor_nogc(&(ce->default_properties_table[parent_num])); - ce->default_properties_table[parent_num] = ce->default_properties_table[child_num]; - ZVAL_UNDEF(&ce->default_properties_table[child_num]); + + if (use_child_prop) { + ZVAL_UNDEF(&ce->default_properties_table[parent_num]); + } else { + int child_num = OBJ_PROP_TO_NUM(child_info->offset); + ce->default_properties_table[parent_num] = ce->default_properties_table[child_num]; + ZVAL_UNDEF(&ce->default_properties_table[child_num]); + } + } else { + /* Default value was removed in child, remove it from parent too. */ + if (ZEND_TYPE_IS_SET(child_info->type)) { + ZVAL_UNDEF(&ce->default_properties_table[parent_num]); + } else { + ZVAL_NULL(&ce->default_properties_table[parent_num]); + } } - child_info->offset = parent_info->offset; + if (!use_child_prop) { + child_info->offset = parent_info->offset; + } child_info->flags &= ~ZEND_ACC_VIRTUAL; } @@ -1675,7 +1702,8 @@ void zend_build_properties_info_table(zend_class_entry *ce) ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop) { if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0 && !(prop->flags & ZEND_ACC_VIRTUAL)) { - table[OBJ_PROP_TO_NUM(prop->offset)] = prop; + uint32_t prop_table_offset = OBJ_PROP_TO_NUM(!(prop->prototype->flags & ZEND_ACC_VIRTUAL) ? prop->prototype->offset : prop->offset); + table[prop_table_offset] = prop; } } ZEND_HASH_FOREACH_END(); } @@ -1689,8 +1717,12 @@ ZEND_API void zend_verify_hooked_property(zend_class_entry *ce, zend_property_in /* We specified a default value (otherwise offset would be -1), but the virtual flag wasn't * removed during inheritance. */ if ((prop_info->flags & ZEND_ACC_VIRTUAL) && prop_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot specify default value for virtual hooked property %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + if (Z_TYPE(ce->default_properties_table[OBJ_PROP_TO_NUM(prop_info->offset)]) == IS_UNDEF) { + prop_info->offset = ZEND_VIRTUAL_PROPERTY_OFFSET; + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot specify default value for virtual hooked property %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + } } /* If the property turns backed during inheritance and no type and default value are set, we want * the default value to be null. */ diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index e6dd6144d4b5c..41ba3e1cd288d 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -806,7 +806,11 @@ zend_property_info *zend_lazy_object_get_property_info_for_slot(zend_object *obj zend_property_info **table = obj->ce->properties_info_table; intptr_t prop_num = slot - obj->properties_table; if (prop_num >= 0 && prop_num < obj->ce->default_properties_count) { - return table[prop_num]; + if (table[prop_num]) { + return table[prop_num]; + } else { + return zend_get_property_info_for_slot_slow(obj, slot); + } } if (!zend_lazy_object_initialized(obj)) { diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index 8a6b714c8b3fd..1ba250bec6439 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -200,3 +200,15 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_del(zend_object *object) /* {{{ * } } /* }}} */ + +ZEND_API ZEND_COLD zend_property_info *zend_get_property_info_for_slot_slow(zend_object *obj, zval *slot) +{ + uintptr_t offset = (uintptr_t)slot - (uintptr_t)obj->properties_table; + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&obj->ce->properties_info, prop_info) { + if (prop_info->offset == offset) { + return prop_info; + } + } ZEND_HASH_FOREACH_END(); + return NULL; +} diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 02326f2b31460..242bf212ba9c6 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -96,6 +96,8 @@ static zend_always_inline void *zend_object_alloc(size_t obj_size, zend_class_en return obj; } +ZEND_API ZEND_COLD zend_property_info *zend_get_property_info_for_slot_slow(zend_object *obj, zval *slot); + /* Use when 'slot' was obtained directly from obj->properties_table, or when * 'obj' can not be lazy. Otherwise, use zend_get_property_info_for_slot(). */ static inline zend_property_info *zend_get_property_info_for_slot_self(zend_object *obj, zval *slot) @@ -103,7 +105,11 @@ static inline zend_property_info *zend_get_property_info_for_slot_self(zend_obje zend_property_info **table = obj->ce->properties_info_table; intptr_t prop_num = slot - obj->properties_table; ZEND_ASSERT(prop_num >= 0 && prop_num < obj->ce->default_properties_count); - return table[prop_num]; + if (table[prop_num]) { + return table[prop_num]; + } else { + return zend_get_property_info_for_slot_slow(obj, slot); + } } static inline zend_property_info *zend_get_property_info_for_slot(zend_object *obj, zval *slot) @@ -114,7 +120,11 @@ static inline zend_property_info *zend_get_property_info_for_slot(zend_object *o zend_property_info **table = obj->ce->properties_info_table; intptr_t prop_num = slot - obj->properties_table; ZEND_ASSERT(prop_num >= 0 && prop_num < obj->ce->default_properties_count); - return table[prop_num]; + if (table[prop_num]) { + return table[prop_num]; + } else { + return zend_get_property_info_for_slot_slow(obj, slot); + } } /* Helper for cases where we're only interested in property info of typed properties. */ diff --git a/ext/opcache/shared_alloc_win32.c b/ext/opcache/shared_alloc_win32.c index 3f0c71a7c6678..086ec4a36f8a6 100644 --- a/ext/opcache/shared_alloc_win32.c +++ b/ext/opcache/shared_alloc_win32.c @@ -284,7 +284,7 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_ } else { char *s = ZCG(accel_directives).mmap_base; - /* skip leading 0x, %p assumes hexdecimal format anyway */ + /* skip leading 0x, %p assumes hexadecimal format anyway */ if (*s == '0' && *(s + 1) == 'x') { s += 2; } diff --git a/ext/pdo_pgsql/tests/pdopgsql_003.phpt b/ext/pdo_pgsql/tests/pdopgsql_003.phpt index 04daaeb6776a8..60a5bcc0775f1 100644 --- a/ext/pdo_pgsql/tests/pdopgsql_003.phpt +++ b/ext/pdo_pgsql/tests/pdopgsql_003.phpt @@ -26,6 +26,6 @@ try { echo $e->getMessage() . "\n"; } ---EXPECT-- +--EXPECTF-- "This is a quote""" -SQLSTATE[HY000]: General error: 7 incomplete multibyte character +SQLSTATE[HY000]: General error: 7 %r(incomplete|invalid)%r multibyte character diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 78315e9880b4f..1149be29bd46e 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -528,7 +528,7 @@ PHP_FUNCTION(spl_autoload_register) if (ZEND_FCI_INITIALIZED(fci)) { if (!fcc.function_handler) { /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal - * with it outselves. It is important that it is not refetched on every call, + * with it ourselves. It is important that it is not refetched on every call, * because calls may occur from different scopes. */ zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL); } @@ -591,7 +591,7 @@ PHP_FUNCTION(spl_autoload_unregister) if (!fcc.function_handler) { /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal - * with it outselves. It is important that it is not refetched on every call, + * with it ourselves. It is important that it is not refetched on every call, * because calls may occur from different scopes. */ zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); } diff --git a/ext/spl/spl_dllist.c b/ext/spl/spl_dllist.c index 5a78db2921a81..0f525e627d305 100644 --- a/ext/spl/spl_dllist.c +++ b/ext/spl/spl_dllist.c @@ -764,7 +764,7 @@ PHP_METHOD(SplDoublyLinkedList, offsetUnset) element = spl_ptr_llist_offset(intern->llist, index, intern->flags & SPL_DLLIST_IT_LIFO); if (element != NULL) { - /* connect the neightbors */ + /* connect the neighbors */ if (element->prev) { element->prev->next = element->next; } diff --git a/ext/standard/crc32_x86.c b/ext/standard/crc32_x86.c index 211ff4da50c29..0b655fd6a4832 100644 --- a/ext/standard/crc32_x86.c +++ b/ext/standard/crc32_x86.c @@ -66,7 +66,7 @@ static uint8_t pclmul_shuf_mask_table[16] = { /* Folding of 128-bit data chunks */ #define CRC32_FOLDING_BLOCK_SIZE (16) -/* PCLMUL version of non-relfected crc32 */ +/* PCLMUL version of non-reflected crc32 */ ZEND_INTRIN_SSE4_2_PCLMUL_FUNC_DECL(size_t crc32_pclmul_batch(uint32_t *crc, const unsigned char *p, size_t nr, const crc32_pclmul_consts *consts)); size_t crc32_pclmul_batch(uint32_t *crc, const unsigned char *p, size_t nr, const crc32_pclmul_consts *consts) { @@ -183,7 +183,7 @@ size_t crc32_pclmul_batch(uint32_t *crc, const unsigned char *p, size_t nr, cons return (nr_in - nr); /* the nr processed */ } -/* PCLMUL version of relfected crc32 */ +/* PCLMUL version of reflected crc32 */ ZEND_INTRIN_SSE4_2_PCLMUL_FUNC_DECL(size_t crc32_pclmul_reflected_batch(uint32_t *crc, const unsigned char *p, size_t nr, const crc32_pclmul_consts *consts)); size_t crc32_pclmul_reflected_batch(uint32_t *crc, const unsigned char *p, size_t nr, const crc32_pclmul_consts *consts) { diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c index 705370a59062a..7cb54aa0aca49 100644 --- a/ext/standard/filestat.c +++ b/ext/standard/filestat.c @@ -39,7 +39,7 @@ #if defined(__APPLE__) /* - Apple statvfs has an interger overflow in libc copying to statvfs. + Apple statvfs has an integer overflow in libc copying to statvfs. cvt_statfs_to_statvfs(struct statfs *from, struct statvfs *to) { to->f_blocks = (fsblkcnt_t)from->f_blocks; */ diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index be33b5d4043d2..84984188f2710 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -38,7 +38,7 @@ #ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP /* Only defined on glibc >= 2.29, FreeBSD CURRENT, musl >= 1.1.24, * MacOS Catalina or later.. - * It should be posible to modify this so it is also + * It should be possible to modify this so it is also * used in older systems when $cwd == NULL but care must be taken * as at least glibc < 2.24 has a legacy implementation known * to be really buggy.