Skip to content

Commit 34db4e5

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 9072a51 commit 34db4e5

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"
@@ -657,7 +673,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
657673
&& !(pi->flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET));
658674
}
659675

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

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

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

0 commit comments

Comments
 (0)