diff --git a/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization.phpt b/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization.phpt new file mode 100644 index 0000000000000..cf17fb7dbde36 --- /dev/null +++ b/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization.phpt @@ -0,0 +1,113 @@ +--TEST-- +Lazy objects: getRawValueWithoutLazyInitialization() fetches raw value without triggering initialization +--FILE-- +getProperty('a'); +$propB = $reflector->getProperty('b'); + +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +print "# Ghost: Lazy properties\n"; + +$isLazy = null; +var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$isLazy = null; +var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +print "# Ghost: Initialized properties\n"; + +$propA->setRawValueWithoutLazyInitialization($obj, 1); + +$isLazy = null; +var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$propB->skipLazyInitialization($obj); + +$isLazy = null; +var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +print "# Proxy: Lazy properties\n"; + +$isLazy = null; +var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$isLazy = null; +var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +print "# Proxy: Initialized properties\n"; + +$propA->setRawValueWithoutLazyInitialization($obj, 1); + +$isLazy = null; +var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$propB->skipLazyInitialization($obj); + +$isLazy = null; +var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); +$reflector->initializeLazyObject($obj); + +print "# Initialized Proxy\n"; + +try { + $propA->getRawValueWithoutLazyInitialization($obj, $isLazy); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +$isLazy = null; +var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy)); +var_dump($isLazy); + +?> +--EXPECT-- +# Ghost: Lazy properties +NULL +bool(true) +NULL +bool(true) +# Ghost: Initialized properties +int(1) +bool(false) +int(2) +bool(false) +# Proxy: Lazy properties +NULL +bool(true) +NULL +bool(true) +# Proxy: Initialized properties +int(1) +bool(false) +int(2) +bool(false) +# Initialized Proxy +Error: Typed property C::$a must not be accessed before initialization +int(2) +bool(false) diff --git a/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization_non_lazy.phpt b/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization_non_lazy.phpt new file mode 100644 index 0000000000000..9cc35a1e194eb --- /dev/null +++ b/Zend/tests/lazy_objects/getRawValueWithoutLazyInitialization_non_lazy.phpt @@ -0,0 +1,140 @@ +--TEST-- +Lazy objects: getRawValueWithoutLazyInitialization() behaves like getRawValue() on non-lazy objects +--FILE-- +d; } + set($value) { $this->d = $value; } + } +} + +class D extends C { + public function __get($name) { + return ord($name); + } +} + +$reflector = new ReflectionClass(C::class); +$propA = $reflector->getProperty('a'); +$propB = $reflector->getProperty('b'); +$propC = $reflector->getProperty('c'); +$propD = $reflector->getProperty('d'); +$propStatic = $reflector->getProperty('static'); + +$obj = new C(); +$obj->dynamic = 1; +$propDynamic = new ReflectionProperty($obj, 'dynamic'); + +$obj = new C(); + +print "# Non initialized properties\n"; + +try { + $propA->getRawValueWithoutLazyInitialization($obj); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump($propB->getRawValueWithoutLazyInitialization($obj)); + +try { + var_dump($propC->getRawValueWithoutLazyInitialization($obj)); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump($propD->getRawValueWithoutLazyInitialization($obj)); + +print "# Initialized properties\n"; + +$obj->a = 1; +$obj->b = new stdClass; +$obj->d = 4; + +var_dump($propA->getRawValueWithoutLazyInitialization($obj)); + +var_dump($propB->getRawValueWithoutLazyInitialization($obj)); + +try { + var_dump($propC->getRawValueWithoutLazyInitialization($obj)); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump($propD->getRawValueWithoutLazyInitialization($obj)); + +try { + $propStatic->getRawValueWithoutLazyInitialization($obj); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump($propDynamic->getRawValueWithoutLazyInitialization($obj)); + +print "# Unset properties and __get()\n"; + +$obj = new D(); +unset($obj->a); +unset($obj->b); + +var_dump($propA->getRawValueWithoutLazyInitialization($obj)); + +var_dump($propB->getRawValueWithoutLazyInitialization($obj)); + +print "# References\n"; + +$obj = new C(); +$obj->b = &$obj; + +var_dump($propB->getRawValueWithoutLazyInitialization($obj)); + +print "# Internal class\n"; + +$reflector = new ReflectionClass(Exception::class); +$propMessage = $reflector->getProperty('message'); + +$obj = new Exception('hello'); + +var_dump($propMessage->getRawValueWithoutLazyInitialization($obj)); + +?> +--EXPECTF-- +# Non initialized properties +Error: Typed property C::$a must not be accessed before initialization +NULL +Error: Must not read from virtual property C::$c +NULL +# Initialized properties +int(1) +object(stdClass)#%d (0) { +} +Error: Must not read from virtual property C::$c +int(4) +ReflectionException: May not use getRawValueWithoutLazyInitialization on static properties + +Warning: Undefined property: C::$dynamic in %s on line %d +NULL +# Unset properties and __get() +int(97) +int(98) +# References +object(C)#%d (2) { + ["a"]=> + uninitialized(int) + ["b"]=> + *RECURSION* + ["d"]=> + NULL +} +# Internal class +string(5) "hello" diff --git a/Zend/tests/lazy_objects/isLazy.phpt b/Zend/tests/lazy_objects/isLazy.phpt new file mode 100644 index 0000000000000..9d944dc5d1eca --- /dev/null +++ b/Zend/tests/lazy_objects/isLazy.phpt @@ -0,0 +1,101 @@ +--TEST-- +Lazy Objects: ReflectionProperty::isLazy() +--FILE-- +dynamic = 1; + $pr = new ReflectionProperty($tmp, $name); + } else { + $pr = $reflector->getProperty($name); + } + printf("%s: %d\n", $name, $pr->isLazy($obj)); + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost\n"; + +$obj = $reflector->newLazyGhost(function () { }); + +testProps($reflector, $obj); + +$pr = $reflector->getProperty('typed'); +$pr->skipLazyInitialization($obj); +printf("typed (skipped): %d\n", $pr->isLazy($obj)); + +print "# Initialized Ghost\n"; + +$reflector->initializeLazyObject($obj); + +testProps($reflector, $obj); + +print "# Proxy\n"; + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +testProps($reflector, $obj); + +$pr = $reflector->getProperty('typed'); +$pr->skipLazyInitialization($obj); +printf("typed (skipped prop): %d\n", $pr->isLazy($obj)); + +print "# Initialized Proxy\n"; + +$reflector->initializeLazyObject($obj); + +testProps($reflector, $obj); + +print "# Internal\n"; + +$obj = (new DateTime())->diff(new DateTime()); +$reflector = new ReflectionClass(DateInterval::class); +$pr = new ReflectionProperty($obj, 'y'); +printf("y: %d\n", $pr->isLazy($obj)); + +?> +--EXPECT-- +# Ghost +staticProp: 0 +typed: 1 +untyped: 1 +virtual: 0 +dynamic: 0 +typed (skipped): 0 +# Initialized Ghost +staticProp: 0 +typed: 0 +untyped: 0 +virtual: 0 +dynamic: 0 +# Proxy +staticProp: 0 +typed: 1 +untyped: 1 +virtual: 0 +dynamic: 0 +typed (skipped prop): 0 +# Initialized Proxy +staticProp: 0 +typed: 0 +untyped: 0 +virtual: 0 +dynamic: 0 +# Internal +y: 0 diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 36edb6b527e15..f664d0c8efa2a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6073,6 +6073,26 @@ ZEND_METHOD(ReflectionProperty, setValue) } /* }}} */ +static void reflection_property_get_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *return_value) +{ + if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + zval rv; + zval *member_p = zend_read_property_ex(intern->ce, object, ref->unmangled_name, 0, &rv); + + if (member_p != &rv) { + RETURN_COPY_DEREF(member_p); + } else { + if (Z_ISREF_P(member_p)) { + zend_unwrap_reference(member_p); + } + RETURN_COPY_VALUE(member_p); + } + } else { + zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name); + zend_call_known_instance_method_with_0_params(func, object, return_value); + } +} + ZEND_METHOD(ReflectionProperty, getRawValue) { reflection_object *intern; @@ -6095,22 +6115,7 @@ ZEND_METHOD(ReflectionProperty, getRawValue) RETURN_THROWS(); } - if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { - zval rv; - zval *member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv); - - if (member_p != &rv) { - RETURN_COPY_DEREF(member_p); - } else { - if (Z_ISREF_P(member_p)) { - zend_unwrap_reference(member_p); - } - RETURN_COPY_VALUE(member_p); - } - } else { - zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name); - zend_call_known_instance_method_with_0_params(func, Z_OBJ_P(object), return_value); - } + reflection_property_get_raw_value(ref, intern, Z_OBJ_P(object), return_value); } static void reflection_property_set_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *value) @@ -6271,6 +6276,83 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization) } } +ZEND_METHOD(ReflectionProperty, getRawValueWithoutLazyInitialization) +{ + reflection_object *intern; + property_reference *ref; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ref); + + ZEND_PARSE_PARAMETERS_START(1, 1) { + Z_PARAM_OBJ_OF_CLASS(object, intern->ce) + } ZEND_PARSE_PARAMETERS_END(); + + if (zend_object_is_lazy_proxy(object) + && zend_lazy_object_initialized(object)) { + object = zend_lazy_object_get_instance(object); + } + + /* Fallback to getRawValue() if object is not lazy and has custom handlers. + * Lazy objects with custom handlers are possible if an extension overrides + * std handlers. */ + if (UNEXPECTED(object->handlers != &std_object_handlers) + && !zend_object_is_lazy(object)) { + goto get_raw_value; + } + + /* Fallback to getRawValue() for dynamic or virtual properties as these can + * not be lazy and code below does not support these. */ + if (!ref->prop || ref->prop->flags & ZEND_ACC_VIRTUAL) { + goto get_raw_value; + } + + if (UNEXPECTED(ref->prop->flags & ZEND_ACC_STATIC)) { + _DO_THROW("May not use getRawValueWithoutLazyInitialization on static properties"); + RETURN_THROWS(); + } + + zval *prop = OBJ_PROP(object, ref->prop->offset); + + if (Z_PROP_FLAG_P(prop) & IS_PROP_LAZY) { + RETURN_NULL(); + } + + /* Fallback to getRawValue() for uninitialized properties */ + if (UNEXPECTED(Z_TYPE_P(prop) == IS_UNDEF)) { + goto get_raw_value; + } + + RETURN_COPY_DEREF(prop); + +get_raw_value: + reflection_property_get_raw_value(ref, intern, object, return_value); +} + +ZEND_METHOD(ReflectionProperty, isLazy) +{ + reflection_object *intern; + property_reference *ref; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ref); + + ZEND_PARSE_PARAMETERS_START(1, 1) { + Z_PARAM_OBJ_OF_CLASS(object, intern->ce) + } ZEND_PARSE_PARAMETERS_END(); + + if (!ref->prop || ref->prop->flags & (ZEND_ACC_STATIC | ZEND_ACC_VIRTUAL)) { + RETURN_FALSE; + } + + if (zend_object_is_lazy_proxy(object) + && zend_lazy_object_initialized(object)) { + object = zend_lazy_object_get_instance(object); + } + + RETURN_BOOL(Z_PROP_FLAG_P(OBJ_PROP(object, ref->prop->offset)) & IS_PROP_LAZY); +} + /* {{{ Returns true if property was initialized */ ZEND_METHOD(ReflectionProperty, isInitialized) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 2b86559168012..3a5dc8626c0c7 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -502,6 +502,10 @@ public function setRawValueWithoutLazyInitialization(object $object, mixed $valu public function skipLazyInitialization(object $object): void {} + public function getRawValueWithoutLazyInitialization(object $object): mixed {} + + public function isLazy(object $object): bool {} + /** @tentative-return-type */ public function isInitialized(?object $object = null): bool {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index f55e96a7ed7ac..d8b8a2b1f439a 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 273777e0bc50a3d5059bb2db7b0a1e293b26e338 */ + * Stub hash: 6d00f21a2021996bc076c27dcb74debfee09560b */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -399,6 +399,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_skipLaz ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionProperty_getRawValueWithoutLazyInitialization arginfo_class_ReflectionProperty_getRawValue + +#define arginfo_class_ReflectionProperty_isLazy arginfo_class_ReflectionClass_isUninitializedLazyObject + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isInitialized, 0, 0, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, object, IS_OBJECT, 1, "null") ZEND_END_ARG_INFO() @@ -843,6 +847,8 @@ ZEND_METHOD(ReflectionProperty, getRawValue); ZEND_METHOD(ReflectionProperty, setRawValue); ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization); ZEND_METHOD(ReflectionProperty, skipLazyInitialization); +ZEND_METHOD(ReflectionProperty, getRawValueWithoutLazyInitialization); +ZEND_METHOD(ReflectionProperty, isLazy); ZEND_METHOD(ReflectionProperty, isInitialized); ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPrivate); @@ -1140,6 +1146,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, setRawValue, arginfo_class_ReflectionProperty_setRawValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, setRawValueWithoutLazyInitialization, arginfo_class_ReflectionProperty_setRawValueWithoutLazyInitialization, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, skipLazyInitialization, arginfo_class_ReflectionProperty_skipLazyInitialization, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, getRawValueWithoutLazyInitialization, arginfo_class_ReflectionProperty_getRawValueWithoutLazyInitialization, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isLazy, arginfo_class_ReflectionProperty_isLazy, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isInitialized, arginfo_class_ReflectionProperty_isInitialized, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPublic, arginfo_class_ReflectionProperty_isPublic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC)