Skip to content

Commit da4bda2

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 0ecb300 commit da4bda2

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"
@@ -652,7 +668,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
652668
&& !(pi->flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET));
653669
}
654670

655-
#if PHP_VERSION_ID >= 80400
671+
#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80600
656672
/* fn_proxy slot cached across calls — first invocation fills it via method
657673
* lookup; subsequent invocations reuse the resolved zend_function*. */
658674
static zend_function *dc_set_raw_no_lazy_fn = NULL;
@@ -661,9 +677,11 @@ static zend_function *dc_set_raw_no_lazy_fn = NULL;
661677
* ReflectionProperty instances). Defined later; forward-declared here. */
662678
static void dc_lazy_refl_cache_dtor(zval *zv);
663679

664-
/* Delegates to ReflectionProperty::setRawValueWithoutLazyInitialization because the
665-
* required engine helpers (zend_lazy_object_decr_lazy_props, _realize) aren't ZEND_API.
666-
* Per-request cache keyed on pi — the ReflectionProperty is per-class, not per-instance. */
680+
/* Pre-PHP-8.6 fallback: PHP 8.6 exposes zend_reflection_property_set_raw_value_
681+
* without_lazy_initialization() as PHPAPI, so the lazy-prop dance lives in one
682+
* place in ext/reflection. On 8.4/8.5 we delegate through a userland
683+
* ReflectionProperty round-trip — construct (cached per pi) + invoke
684+
* setRawValueWithoutLazyInitialization($obj, $value). */
667685
static bool dc_set_raw_value_without_lazy_init(zend_object *obj,
668686
zend_property_info *pi, zend_string *name, zval *value)
669687
{
@@ -810,17 +828,23 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
810828
#if PHP_VERSION_ID >= 80400
811829
/* Skip the Reflection round-trip when there's no lazy-init to skip. */
812830
if (no_lazy_init && !zend_lazy_object_initialized(obj)) {
831+
# if PHP_VERSION_ID >= 80600
832+
zend_reflection_property_set_raw_value_without_lazy_initialization(
833+
pi, name, NULL, pi->ce, obj, value);
834+
bool ok = !EG(exception);
835+
# else
813836
bool ok = dc_set_raw_value_without_lazy_init(obj, pi, name, value);
814-
#if PHP_VERSION_ID >= 80100
837+
# endif
838+
# if PHP_VERSION_ID >= 80100
815839
if (enum_holder_used) {
816840
zval_ptr_dtor(&enum_holder);
817841
}
818-
#endif
842+
# endif
819843
return ok;
820844
}
821845
#endif
822846

823-
if (!ZEND_TYPE_IS_SET(pi->type) && !DC_PROP_HAS_HOOKS(pi)) {
847+
if (!ZEND_TYPE_IS_SET(pi->type) && !DC_PROP_HAS_HOOKS(pi) && !call_hooks) {
824848
/* Move the old value out before running its destructor: a __destruct
825849
* on the old value can legitimately read (or reassign) this same slot.
826850
* Install the new value first so reentrant reads see a valid slot. */
@@ -829,7 +853,14 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
829853
ZVAL_COPY(slot, value);
830854
zval_ptr_dtor(&old);
831855
}
832-
#if PHP_VERSION_ID >= 80400
856+
#if PHP_VERSION_ID >= 80600
857+
else if (!call_hooks) {
858+
/* Default mode: setRawValue semantics (bypass set hook on hooked
859+
* non-virtual, type-check on typed). One PHPAPI call replaces our
860+
* old trampoline + zend_update_property_ex split. */
861+
zend_reflection_property_set_raw_value(pi, name, NULL, pi->ce, obj, value);
862+
}
863+
#elif PHP_VERSION_ID >= 80400
833864
else if (!call_hooks && DC_PROP_HAS_HOOKS(pi) && pi->hooks[ZEND_PROPERTY_HOOK_SET]) {
834865
zend_function *trampoline = zend_get_property_hook_trampoline(
835866
pi, ZEND_PROPERTY_HOOK_SET, name);

0 commit comments

Comments
 (0)