Skip to content

Commit aec9c46

Browse files
Use zend_reflection_property_set_raw_value_* PHPAPI helpers when available
PHP 8.6 exposes zend_reflection_property_set_raw_value{,_without_lazy_initialization} as PHPAPI (php/php-src#21763), moving the lazy-prop bookkeeping into ext/reflection. Use the PHPAPI direct on 8.6+ and keep the userland ReflectionProperty round-trip as a fallback for 8.4 / 8.5. Depends on php/php-src#21763 landing; holding here until the patch is merged and the target PHP version is known.
1 parent 41398c2 commit aec9c46

File tree

1 file changed

+39
-8
lines changed

1 file changed

+39
-8
lines changed

deepclone.c

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ extern PHPAPI zend_class_entry *reflector_ptr;
6969
extern PHPAPI zend_class_entry *reflection_type_ptr;
7070
extern PHPAPI zend_class_entry *reflection_property_ptr;
7171

72+
/* PHPAPI helpers exposed by ext/reflection in PHP 8.6+. They encapsulate the
73+
* setRawValue / setRawValueWithoutLazyInitialization logic — including the
74+
* trampoline-based hook bypass and lazy-prop/realize handling — that we
75+
* previously had to either re-implement or delegate to via a userland
76+
* ReflectionProperty round-trip. */
77+
#if PHP_VERSION_ID >= 80600
78+
extern PHPAPI void zend_reflection_property_set_raw_value(
79+
const zend_property_info *prop, zend_string *unmangled_name,
80+
void *cache_slot[3], const zend_class_entry *scope,
81+
zend_object *object, zval *value);
82+
extern PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization(
83+
const zend_property_info *prop, zend_string *unmangled_name,
84+
void *cache_slot[3], const zend_class_entry *scope,
85+
zend_object *object, zval *value);
86+
#endif
87+
7288
/* ── Compatibility shims for older PHP versions ────────────── */
7389

7490
/* zend_zval_value_name() landed in PHP 8.3 (returns "true"/"false"/"null"
@@ -656,7 +672,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
656672
&& !(pi->flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET));
657673
}
658674

659-
#if PHP_VERSION_ID >= 80400
675+
#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80600
660676
/* fn_proxy slot cached across calls — first invocation fills it via method
661677
* lookup; subsequent invocations reuse the resolved zend_function*. */
662678
static zend_function *dc_set_raw_no_lazy_fn = NULL;
@@ -665,9 +681,11 @@ static zend_function *dc_set_raw_no_lazy_fn = NULL;
665681
* ReflectionProperty instances). Defined later; forward-declared here. */
666682
static void dc_lazy_refl_cache_dtor(zval *zv);
667683

668-
/* Delegates to ReflectionProperty::setRawValueWithoutLazyInitialization because the
669-
* required engine helpers (zend_lazy_object_decr_lazy_props, _realize) aren't ZEND_API.
670-
* Per-request cache keyed on pi — the ReflectionProperty is per-class, not per-instance. */
684+
/* Pre-PHP-8.6 fallback: PHP 8.6 exposes zend_reflection_property_set_raw_value_
685+
* without_lazy_initialization() as PHPAPI, so the lazy-prop dance lives in one
686+
* place in ext/reflection. On 8.4/8.5 we delegate through a userland
687+
* ReflectionProperty round-trip — construct (cached per pi) + invoke
688+
* setRawValueWithoutLazyInitialization($obj, $value). */
671689
static bool dc_set_raw_value_without_lazy_init(zend_object *obj,
672690
zend_property_info *pi, zend_string *name, zval *value)
673691
{
@@ -795,17 +813,23 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
795813
#if PHP_VERSION_ID >= 80400
796814
/* Skip the Reflection round-trip when there's no lazy-init to skip. */
797815
if (no_lazy_init && !zend_lazy_object_initialized(obj)) {
816+
# if PHP_VERSION_ID >= 80600
817+
zend_reflection_property_set_raw_value_without_lazy_initialization(
818+
pi, name, NULL, pi->ce, obj, value);
819+
bool ok = !EG(exception);
820+
# else
798821
bool ok = dc_set_raw_value_without_lazy_init(obj, pi, name, value);
799-
#if PHP_VERSION_ID >= 80100
822+
# endif
823+
# if PHP_VERSION_ID >= 80100
800824
if (enum_holder_used) {
801825
zval_ptr_dtor(&enum_holder);
802826
}
803-
#endif
827+
# endif
804828
return ok;
805829
}
806830
#endif
807831

808-
if (!ZEND_TYPE_IS_SET(pi->type) && !DC_PROP_HAS_HOOKS(pi)) {
832+
if (!ZEND_TYPE_IS_SET(pi->type) && !DC_PROP_HAS_HOOKS(pi) && !call_hooks) {
809833
/* Move the old value out before running its destructor: a __destruct
810834
* on the old value can legitimately read (or reassign) this same slot.
811835
* Install the new value first so reentrant reads see a valid slot. */
@@ -814,7 +838,14 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
814838
ZVAL_COPY(slot, value);
815839
zval_ptr_dtor(&old);
816840
}
817-
#if PHP_VERSION_ID >= 80400
841+
#if PHP_VERSION_ID >= 80600
842+
else if (!call_hooks) {
843+
/* Default mode: setRawValue semantics (bypass set hook on hooked
844+
* non-virtual, type-check on typed). One PHPAPI call replaces our
845+
* old trampoline + zend_update_property_ex split. */
846+
zend_reflection_property_set_raw_value(pi, name, NULL, pi->ce, obj, value);
847+
}
848+
#elif PHP_VERSION_ID >= 80400
818849
else if (!call_hooks && DC_PROP_HAS_HOOKS(pi) && pi->hooks[ZEND_PROPERTY_HOOK_SET]) {
819850
zend_function *trampoline = zend_get_property_hook_trampoline(
820851
pi, ZEND_PROPERTY_HOOK_SET, name);

0 commit comments

Comments
 (0)