diff --git a/Zend/tests/lazy_objects/clone_calls___clone_once.phpt b/Zend/tests/lazy_objects/clone_calls___clone_once.phpt new file mode 100644 index 0000000000000..c0f91e3e27a42 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_calls___clone_once.phpt @@ -0,0 +1,77 @@ +--TEST-- +Lazy objects: clone calls __clone() once +--FILE-- +isUninitializedLazyObject($obj)); + var_dump($obj); + var_dump($reflector->isUninitializedLazyObject($clone)); + var_dump($clone); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(5) "clone" +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +string(5) "clone" +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_001.phpt b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_001.phpt new file mode 100644 index 0000000000000..10aab3da023b3 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +Lazy objects: clone is independant of the original object +--FILE-- +newLazyProxy($initializer); +$reflector->getProperty('foo')->skipLazyInitialization($myProxy); + +$clonedProxy = clone $myProxy; +var_dump($clonedProxy->foo); + +$reflector->initializeLazyObject($myProxy); +$myProxy->foo = 'B'; + +$reflector->initializeLazyObject($clonedProxy); + +var_dump($myProxy->foo); +var_dump($clonedProxy->foo); + +--EXPECT-- +string(1) "A" +string(1) "B" +string(1) "A" diff --git a/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_002.phpt b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_002.phpt new file mode 100644 index 0000000000000..8b0016f1e9860 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_002.phpt @@ -0,0 +1,56 @@ +--TEST-- +Lazy objects: clone is independant of the original object +--FILE-- +value = new Value(); + } + public function __clone() { + $this->value = clone $this->value; + } +} + +class Value { + public string $value = 'A'; +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $reflector = new ReflectionClass(SomeObj::class); + + $clonedObj = clone $obj; + var_dump($clonedObj->value->value); + + $reflector->initializeLazyObject($obj); + $obj->value->value = 'B'; + + $reflector->initializeLazyObject($clonedObj); + + var_dump($obj->value->value); + var_dump($clonedObj->value->value); +} + +$reflector = new ReflectionClass(SomeObj::class); + +test('Ghost', $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +})); + +test('Proxy', $reflector->newLazyProxy(function () { + return new SomeObj(); +})); + +?> +--EXPECT-- +# Ghost: +string(1) "A" +string(1) "B" +string(1) "A" +# Proxy: +string(1) "A" +string(1) "B" +string(1) "A" diff --git a/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_003.phpt b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_003.phpt new file mode 100644 index 0000000000000..04d61d7834ca6 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_creates_object_with_independent_state_003.phpt @@ -0,0 +1,40 @@ +--TEST-- +Lazy objects: clone is independant of the original object +--FILE-- +initializeLazyObject($obj); + $reflector->getProperty('foo')->setRawValueWithoutLazyInitialization($clonedObj, 'Y'); + + $reflector->initializeLazyObject($clonedObj); + + var_dump($clonedObj->foo); +} + +$reflector = new ReflectionClass(SomeObj::class); + +test('Ghost', $reflector->newLazyGhost(function ($obj) { +})); + +test('Proxy', $reflector->newLazyProxy(function () { + return new SomeObj(); +})); + +?> +--EXPECT-- +# Ghost: +string(1) "Y" +# Proxy: +string(1) "Y" diff --git a/Zend/tests/lazy_objects/clone_initialized.phpt b/Zend/tests/lazy_objects/clone_initialized.phpt new file mode 100644 index 0000000000000..9540a0f960964 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_initialized.phpt @@ -0,0 +1,73 @@ +--TEST-- +Lazy objects: clone of initialized lazy object does not initialize twice +--FILE-- +initializeLazyObject($obj); + + $clone = clone $obj; + + var_dump($reflector->isUninitializedLazyObject($obj)); + var_dump($obj); + var_dump($reflector->isUninitializedLazyObject($clone)); + var_dump($clone); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/clone_initializer_exception.phpt b/Zend/tests/lazy_objects/clone_initializer_exception.phpt new file mode 100644 index 0000000000000..72069ca650d12 --- /dev/null +++ b/Zend/tests/lazy_objects/clone_initializer_exception.phpt @@ -0,0 +1,56 @@ +--TEST-- +Lazy objects: clone: initializer exception +--FILE-- +getMessage()); + } + + var_dump($reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +Exception: initializer +bool(true) +lazy ghost object(C)#%d (0) { +} +# Proxy: +Exception: initializer +bool(true) +lazy proxy object(C)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/clone_initializes.phpt b/Zend/tests/lazy_objects/clone_initializes.phpt new file mode 100644 index 0000000000000..54be97fd768aa --- /dev/null +++ b/Zend/tests/lazy_objects/clone_initializes.phpt @@ -0,0 +1,71 @@ +--TEST-- +Lazy objects: clone initializes object +--FILE-- +isUninitializedLazyObject($obj)); + var_dump($obj); + var_dump($reflector->isUninitializedLazyObject($clone)); + var_dump($clone); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/clone_preverves_object_class.phpt b/Zend/tests/lazy_objects/clone_preverves_object_class.phpt new file mode 100644 index 0000000000000..40dfbab6ddaee --- /dev/null +++ b/Zend/tests/lazy_objects/clone_preverves_object_class.phpt @@ -0,0 +1,36 @@ +--TEST-- +Lazy objects: clone returns an object of the same class +--FILE-- +foo(); } + +$r = new ReflectionClass(B::class); +$b = $r->newLazyProxy(function ($obj) { + return new A('value'); +}); + +$b->property = 'init_please'; + +$clone = clone $b; +only_b($b); +only_b($clone); + +var_dump($b::class); +var_dump($clone::class); + +?> +==DONE== +--EXPECT-- +string(1) "B" +string(1) "B" +==DONE== diff --git a/Zend/tests/lazy_objects/convert_to_array.phpt b/Zend/tests/lazy_objects/convert_to_array.phpt new file mode 100644 index 0000000000000..72eb3fd4f670e --- /dev/null +++ b/Zend/tests/lazy_objects/convert_to_array.phpt @@ -0,0 +1,93 @@ +--TEST-- +Lazy objects: Convertion to array +--FILE-- +a = 1; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 3); + + $a = []; + // Converts $obj to array internally + array_splice($a, 0, 0, $obj); + var_dump($a, $obj); + + $reflector->initializeLazyObject($obj); + + $a = []; + array_splice($a, 0, 0, $obj); + var_dump($a, $obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +array(1) { + [0]=> + int(3) +} +lazy ghost object(C)#%d (1) { + ["a"]=> + int(3) +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +# Proxy +array(1) { + [0]=> + int(3) +} +lazy proxy object(C)#%d (1) { + ["a"]=> + int(3) +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/dtor_called_if_init.phpt b/Zend/tests/lazy_objects/dtor_called_if_init.phpt new file mode 100644 index 0000000000000..19a9c7b941b00 --- /dev/null +++ b/Zend/tests/lazy_objects/dtor_called_if_init.phpt @@ -0,0 +1,66 @@ +--TEST-- +Lazy objects: destructor of initialized objets is called +--FILE-- +newLazyGhost(function () { + var_dump("initializer"); + }); + print "After makeLazy\n"; + + var_dump($obj->a); +} + +function proxy() { + $reflector = new ReflectionClass(C::class); + + print "# Proxy:\n"; + + print "In makeLazy\n"; + $obj = $reflector->newLazyProxy(function () { + var_dump("initializer"); + return new C(); + }); + print "After makeLazy\n"; + + var_dump($obj->a); +} + +ghost(); +proxy(); + +--EXPECTF-- +# Ghost: +In makeLazy +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +In makeLazy +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" +object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/dtor_not_called_if_not_init.phpt b/Zend/tests/lazy_objects/dtor_not_called_if_not_init.phpt new file mode 100644 index 0000000000000..f0ea2fdfc1ba2 --- /dev/null +++ b/Zend/tests/lazy_objects/dtor_not_called_if_not_init.phpt @@ -0,0 +1,43 @@ +--TEST-- +Lazy objects: destructor of lazy objets is not called if not initialized +--FILE-- +newLazyGhost(function () { + var_dump("initializer"); +}); +print "After newLazyGhost\n"; + +// Does not call destructor +$obj = null; + +print "# Proxy:\n"; + +print "In newLazyProxy\n"; +$obj = $reflector->newLazyProxy(function () { + var_dump("initializer"); +}); +print "After newLazyGhost\n"; + +// Does not call destructor +$obj = null; + +--EXPECT-- +# Ghost: +In newLazyGhost +After newLazyGhost +# Proxy: +In newLazyProxy +After newLazyGhost diff --git a/Zend/tests/lazy_objects/fetch_coalesce_initializes.phpt b/Zend/tests/lazy_objects/fetch_coalesce_initializes.phpt new file mode 100644 index 0000000000000..f2a4123c1b2ac --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_coalesce_initializes.phpt @@ -0,0 +1,63 @@ +--TEST-- +Lazy objects: property fetch coalesce initializes object +--FILE-- +a ?? null); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_coalesce_non_existing_initializes.phpt b/Zend/tests/lazy_objects/fetch_coalesce_non_existing_initializes.phpt new file mode 100644 index 0000000000000..1ac947f93d65e --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_coalesce_non_existing_initializes.phpt @@ -0,0 +1,63 @@ +--TEST-- +Lazy objects: property fetch coalesce on non existing property initializes object +--FILE-- +unknown ?? null); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_declared_prop_initializes.phpt b/Zend/tests/lazy_objects/fetch_declared_prop_initializes.phpt new file mode 100644 index 0000000000000..da3540bacc578 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_declared_prop_initializes.phpt @@ -0,0 +1,72 @@ +--TEST-- +Lazy objects: property fetch initializes object +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/fetch_dynamic_prop_initializes.phpt b/Zend/tests/lazy_objects/fetch_dynamic_prop_initializes.phpt new file mode 100644 index 0000000000000..a8171bcd6eb6c --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_dynamic_prop_initializes.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: property fetch of dynamic property initializes object +--FILE-- +dynamic); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (0) { + ["a"]=> + uninitialized(int) + } +} diff --git a/Zend/tests/lazy_objects/fetch_hook_may_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_hook_may_not_initialize.phpt new file mode 100644 index 0000000000000..b81635dfb9c84 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_hook_may_not_initialize.phpt @@ -0,0 +1,75 @@ +--TEST-- +Lazy objects: hooked property fetch does not initialize object if hook does not observe object state +--FILE-- +a; } + set($value) { $this->a = $value; } + } + public int $b = 1; + + public function __construct(int $a) { + var_dump(__METHOD__); + $this->a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/fetch_hook_virtual_may_initialize.phpt b/Zend/tests/lazy_objects/fetch_hook_virtual_may_initialize.phpt new file mode 100644 index 0000000000000..f66f9a34a680b --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_hook_virtual_may_initialize.phpt @@ -0,0 +1,76 @@ +--TEST-- +Lazy objects: virtual hooked property fetch may initialize object if hook observes object state +--FILE-- +_a; } + } + public int $b = 1; + + public function __construct(int $a) { + var_dump(__METHOD__); + $this->_a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $a = &$obj->a; + var_dump($a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (2) { + ["_a"]=> + &int(1) + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["_a"]=> + &int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/fetch_hook_virtual_may_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_hook_virtual_may_not_initialize.phpt new file mode 100644 index 0000000000000..adf3c2845f21a --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_hook_virtual_may_not_initialize.phpt @@ -0,0 +1,62 @@ +--TEST-- +Lazy objects: virtual hooked property fetch does not initialize object if hook does not observe object state +--FILE-- +b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +int(1) +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +int(1) +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/fetch_magic_prop_may_initialize.phpt b/Zend/tests/lazy_objects/fetch_magic_prop_may_initialize.phpt new file mode 100644 index 0000000000000..491b26dbbee82 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_magic_prop_may_initialize.phpt @@ -0,0 +1,66 @@ +--TEST-- +Lazy objects: magic property fetch initializes object if magic method observes object state +--FILE-- +a; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->magic); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_magic_prop_may_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_magic_prop_may_not_initialize.phpt new file mode 100644 index 0000000000000..3cd283705ab2b --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_magic_prop_may_not_initialize.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: magic property fetch does not not initialize object if magic method does not observe object state +--FILE-- +magic); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(5) "magic" +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(5) "magic" +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/fetch_magic_prop_recursive_may_initialize.phpt b/Zend/tests/lazy_objects/fetch_magic_prop_recursive_may_initialize.phpt new file mode 100644 index 0000000000000..db605826f64e5 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_magic_prop_recursive_may_initialize.phpt @@ -0,0 +1,70 @@ +--TEST-- +Lazy objects: recursive magic property fetch initializes object if magic method observes object state +--FILE-- +$name; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->magic); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" + +Warning: Undefined property: C::$magic in %s on line %d +NULL +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" + +Warning: Undefined property: C::$magic in %s on line %d +NULL +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_op_dynamic_error.phpt b/Zend/tests/lazy_objects/fetch_op_dynamic_error.phpt new file mode 100644 index 0000000000000..f6f4cddedc63e --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_op_dynamic_error.phpt @@ -0,0 +1,62 @@ +--TEST-- +Lazy objects: dynamic property op error +--FILE-- +dynamic++); + } catch(Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new Error("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new Error("initializer"); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +Error: initializer +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +Error: initializer +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/fetch_op_dynamic_prop_initializes.phpt b/Zend/tests/lazy_objects/fetch_op_dynamic_prop_initializes.phpt new file mode 100644 index 0000000000000..3f9af1aa5f1dd --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_op_dynamic_prop_initializes.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: dynamic property op initializes object +--FILE-- +dynamic++); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +object(C)#%d (2) { + ["a"]=> + int(1) + ["dynamic"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["dynamic"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_op_error.phpt b/Zend/tests/lazy_objects/fetch_op_error.phpt new file mode 100644 index 0000000000000..4ccfef4ac4d3a --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_op_error.phpt @@ -0,0 +1,62 @@ +--TEST-- +Lazy objects: property op error +--FILE-- +a = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + + try { + var_dump($obj->a++); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new Error("initializer"); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new Error("initializer"); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +Error: initializer +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +Error: initializer +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/fetch_op_initializes.phpt b/Zend/tests/lazy_objects/fetch_op_initializes.phpt new file mode 100644 index 0000000000000..d1a8605eb1d7f --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_op_initializes.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: property op initializes object +--FILE-- +a = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump($obj->a++); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(2) +object(C)#%d (1) { + ["a"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(2) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%s (1) { + ["a"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/fetch_op_skipped_prop_does_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_op_skipped_prop_does_not_initialize.phpt new file mode 100644 index 0000000000000..c1e662eaef1a3 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_op_skipped_prop_does_not_initialize.phpt @@ -0,0 +1,91 @@ +--TEST-- +Lazy objects: property op on skipped property does not initialize object +--FILE-- +a = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $reflector = new ReflectionClass($obj); + $reflector->getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + $reflector->getProperty('c')->skipLazyInitialization($obj); + + var_dump($obj); + var_dump($obj->a++); + var_dump($obj->b++); + try { + var_dump($obj->c++); + } catch (Error $e) { + printf("%s\n", $e->getMessage()); + } + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new c(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +NULL +int(1) +Typed property C::$c must not be accessed before initialization +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["c"]=> + uninitialized(int) +} +# Proxy: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +NULL +int(1) +Typed property C::$c must not be accessed before initialization +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["c"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/fetch_ref_initializes.phpt b/Zend/tests/lazy_objects/fetch_ref_initializes.phpt new file mode 100644 index 0000000000000..e1289558aaa77 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_ref_initializes.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: property fetch ref initializes object +--FILE-- +a; + var_dump($ref); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (1) { + ["a"]=> + &int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + &int(1) + } +} diff --git a/Zend/tests/lazy_objects/fetch_ref_skipped_prop_does_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_ref_skipped_prop_does_not_initialize.phpt new file mode 100644 index 0000000000000..c64cd88781a31 --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_ref_skipped_prop_does_not_initialize.phpt @@ -0,0 +1,87 @@ +--TEST-- +Lazy objects: fetch ref on skipped property does not initialize object +--FILE-- +a; + $ref = &$obj->b; + try { + $ref = &$obj->c; + } catch (Error $e) { + printf("%s\n", $e->getMessage()); + } + var_dump($ref); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +Cannot access uninitialized non-nullable property C::$c by reference +int(1) +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + &int(1) + ["c"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +Cannot access uninitialized non-nullable property C::$c by reference +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + &int(1) + ["c"]=> + uninitialized(int) + } +} diff --git a/Zend/tests/lazy_objects/fetch_skipped_prop_does_not_initialize.phpt b/Zend/tests/lazy_objects/fetch_skipped_prop_does_not_initialize.phpt new file mode 100644 index 0000000000000..e93a65ee87cec --- /dev/null +++ b/Zend/tests/lazy_objects/fetch_skipped_prop_does_not_initialize.phpt @@ -0,0 +1,93 @@ +--TEST-- +Lazy objects: fetch skipped property does not initialize object +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $reflector = new ReflectionClass($obj); + $reflector->getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + $reflector->getProperty('c')->skipLazyInitialization($obj); + + var_dump($obj); + var_dump($obj->a); + var_dump($obj->b); + try { + var_dump($obj->c); + } catch (Error $e) { + printf("%s\n", $e->getMessage()); + } + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +NULL +int(1) +Typed property C::$c must not be accessed before initialization +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +# Proxy: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +NULL +int(1) +Typed property C::$c must not be accessed before initialization +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/final_classes_can_be_initialized_lazily.phpt b/Zend/tests/lazy_objects/final_classes_can_be_initialized_lazily.phpt new file mode 100644 index 0000000000000..2434e69f57349 --- /dev/null +++ b/Zend/tests/lazy_objects/final_classes_can_be_initialized_lazily.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: final classes can be initialized lazily +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/gc_001.phpt b/Zend/tests/lazy_objects/gc_001.phpt new file mode 100644 index 0000000000000..7707423815a5b --- /dev/null +++ b/Zend/tests/lazy_objects/gc_001.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: GC 001 +--FILE-- +newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) { + }); + + $canary = null; + $obj = null; + + gc_collect_cycles(); +} + +function proxy() { + printf("# Proxy:\n"); + $canary = new Canary(); + + $obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) { + return new C(); + }); + + $canary = null; + $obj = null; + + gc_collect_cycles(); +} + +ghost(); +proxy(); + +?> +==DONE== +--EXPECT-- +# Ghost: +string(10) "__destruct" +# Proxy: +string(10) "__destruct" +==DONE== diff --git a/Zend/tests/lazy_objects/gc_002.phpt b/Zend/tests/lazy_objects/gc_002.phpt new file mode 100644 index 0000000000000..c794e4077c2bc --- /dev/null +++ b/Zend/tests/lazy_objects/gc_002.phpt @@ -0,0 +1,59 @@ +--TEST-- +Lazy objects: GC 002 +--FILE-- +newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) { + }); + + $canary->value = $obj; + $canary = null; + $obj = null; + + gc_collect_cycles(); +} + +function proxy() { + printf("# Proxy:\n"); + + $canary = new Canary(); + + $obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) { + return new C(); + }); + + $canary->value = $obj; + $canary = null; + $obj = null; + + gc_collect_cycles(); +} + +ghost(); +proxy(); + +?> +==DONE== +--EXPECT-- +# Ghost: +string(10) "__destruct" +# Proxy: +string(10) "__destruct" +==DONE== diff --git a/Zend/tests/lazy_objects/gc_003.phpt b/Zend/tests/lazy_objects/gc_003.phpt new file mode 100644 index 0000000000000..ce58398e509e3 --- /dev/null +++ b/Zend/tests/lazy_objects/gc_003.phpt @@ -0,0 +1,59 @@ +--TEST-- +Lazy objects: GC 003 +--FILE-- +newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) { + }); + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +function proxy() { + printf("# Proxy:\n"); + + $canary = new Canary(); + + $obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) { + return new C(); + }); + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +ghost(); +proxy(); + +?> +==DONE== +--EXPECT-- +# Ghost: +string(10) "__destruct" +# Proxy: +string(10) "__destruct" +==DONE== diff --git a/Zend/tests/lazy_objects/gc_004.phpt b/Zend/tests/lazy_objects/gc_004.phpt new file mode 100644 index 0000000000000..ab0b5c2996770 --- /dev/null +++ b/Zend/tests/lazy_objects/gc_004.phpt @@ -0,0 +1,67 @@ +--TEST-- +Lazy objects: GC 004 +--FILE-- +newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyGhost($obj, function () use ($canary) { + }); + + var_dump($obj); // initializes property hash + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +function proxy() { + printf("# Proxy:\n"); + + $canary = new Canary(); + + $obj = (new ReflectionClass(C::class))->newInstanceWithoutConstructor(); + (new ReflectionClass($obj))->resetAsLazyProxy($obj, function () use ($canary) { + return new C(); + }); + + var_dump($obj); // initializes property hash + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +ghost(); +proxy(); + +?> +==DONE== +--EXPECTF-- +# Ghost: +object(C)#%d (0) { +} +string(10) "__destruct" +# Proxy: +object(C)#%d (0) { +} +string(10) "__destruct" +==DONE== diff --git a/Zend/tests/lazy_objects/gc_005.phpt b/Zend/tests/lazy_objects/gc_005.phpt new file mode 100644 index 0000000000000..934439adab951 --- /dev/null +++ b/Zend/tests/lazy_objects/gc_005.phpt @@ -0,0 +1,68 @@ +--TEST-- +Lazy objects: GC 005 +--FILE-- +newInstanceWithoutConstructor(); + $reflector->resetAsLazyGhost($obj, function () use ($canary) { + }); + + $reflector->getProperty('value')->setRawValueWithoutLazyInitialization($obj, $obj); + $reflector = null; + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +function proxy() { + printf("# Proxy:\n"); + + $canary = new Canary(); + + $reflector = new ReflectionClass(C::class); + $obj = $reflector->newInstanceWithoutConstructor(); + $reflector->resetAsLazyProxy($obj, function () use ($canary) { + return new C(); + }); + + $reflector->getProperty('value')->setRawValueWithoutLazyInitialization($obj, $obj); + $reflector = null; + + $canary->value = $obj; + $obj = null; + $canary = null; + + gc_collect_cycles(); +} + +ghost(); +proxy(); + +?> +==DONE== +--EXPECT-- +# Ghost: +string(10) "__destruct" +# Proxy: +string(10) "__destruct" +==DONE== diff --git a/Zend/tests/lazy_objects/gc_006.phpt b/Zend/tests/lazy_objects/gc_006.phpt new file mode 100644 index 0000000000000..e1002ffea7108 --- /dev/null +++ b/Zend/tests/lazy_objects/gc_006.phpt @@ -0,0 +1,36 @@ +--TEST-- +Lazy objects: GC 006 +--FILE-- +foo = $this; + var_dump(__METHOD__); + } + public function __destruct() { + var_dump(__METHOD__); + } +} + +$reflector = new ReflectionClass(Foo::class); +$foo = $reflector->newLazyGhost(new Initializer()); + +print "Dump\n"; + +var_dump($foo->foo); + +print "Done\n"; + +?> +--EXPECTF-- +Dump +string(21) "Initializer::__invoke" +object(Initializer)#%d (0) { +} +Done +string(23) "Initializer::__destruct" diff --git a/Zend/tests/lazy_objects/get_initializer.phpt b/Zend/tests/lazy_objects/get_initializer.phpt new file mode 100644 index 0000000000000..30b410be0a6f9 --- /dev/null +++ b/Zend/tests/lazy_objects/get_initializer.phpt @@ -0,0 +1,52 @@ +--TEST-- +Lazy objects: getLazyInitializer() returns initializer +--FILE-- +init(...), +]; + +foreach ($initializers as $i => $initializer) { + $c = $reflector->newLazyGhost($initializer); + if ($reflector->getLazyInitializer($c) !== $initializer) { + printf("Initializer %d: failed\n", $i); + continue; + } + + $reflector->initializeLazyObject($c); + if ($reflector->getLazyInitializer($c) !== null) { + printf("Initializer %d: failed\n", $i); + continue; + } + + printf("Initializer %d: ok\n", $i); +} + +?> +--EXPECT-- +Initializer 0: ok +Initializer 1: ok +Initializer 2: ok +Initializer 3: ok +Initializer 4: ok +Initializer 5: ok +Initializer 6: ok diff --git a/Zend/tests/lazy_objects/get_properties.phpt b/Zend/tests/lazy_objects/get_properties.phpt new file mode 100644 index 0000000000000..38b752c2f8b5b --- /dev/null +++ b/Zend/tests/lazy_objects/get_properties.phpt @@ -0,0 +1,27 @@ +--TEST-- +Lazy objects: get_properties failure +--FILE-- +newLazyProxy(function () { + throw new \Exception('Initializer'); +}); + +$b = new C(); + +try { + var_dump($a > $b); +} catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +?> +--EXPECT-- +Exception: Initializer diff --git a/Zend/tests/lazy_objects/init_ast_const.phpt b/Zend/tests/lazy_objects/init_ast_const.phpt new file mode 100644 index 0000000000000..018c46e4915e5 --- /dev/null +++ b/Zend/tests/lazy_objects/init_ast_const.phpt @@ -0,0 +1,27 @@ +--TEST-- +Lazy objects: Class constants are updated before initialization +--FILE-- +newLazyGhost(function () { }); + +function f() { + define('FOO', new stdClass); +} + +f(); + +try { + var_dump($c->a); +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECTF-- +object(stdClass)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/init_ast_const_failure.phpt b/Zend/tests/lazy_objects/init_ast_const_failure.phpt new file mode 100644 index 0000000000000..d3f1ca0f35f5c --- /dev/null +++ b/Zend/tests/lazy_objects/init_ast_const_failure.phpt @@ -0,0 +1,26 @@ +--TEST-- +Lazy objects: Class constants are updated before initialization: update constant failure +--FILE-- +newLazyGhost(function () { }); + +function f() { + define('FOO', new stdClass); +} + +f(); + +try { + var_dump($c->a); +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECT-- +TypeError: Cannot assign stdClass to property C::$a of type C diff --git a/Zend/tests/lazy_objects/init_exception_001.phpt b/Zend/tests/lazy_objects/init_exception_001.phpt new file mode 100644 index 0000000000000..81469b9992d6b --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +Lazy objects: init exception 001 +--FILE-- +newLazyGhost(function ($obj) use ($i) { + if ($i === 1) { + throw new \Exception(); + } + }); + $obj->c = 1; +} + +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): {closure:%s:%d}(Object(C)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/lazy_objects/init_exception_leaves_object_lazy.phpt b/Zend/tests/lazy_objects/init_exception_leaves_object_lazy.phpt new file mode 100644 index 0000000000000..86ecc2c9b8e2e --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_leaves_object_lazy.phpt @@ -0,0 +1,53 @@ +--TEST-- +Lazy objects: Object is still lazy after initializer exception +--FILE-- +initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + throw new Exception('initializer exception'); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +string(11) "initializer" +initializer exception +Is lazy: 1 +# Proxy: +string(11) "initializer" +initializer exception +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes.phpt new file mode 100644 index 0000000000000..4edf9481ebc22 --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes.phpt @@ -0,0 +1,74 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception +--FILE-- +setRawValueWithoutLazyInitialization($obj, 0); + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump($obj); + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + throw new Exception('initializer exception'); +}); + +// Initializer effects on the proxy are not reverted +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +initializer exception +lazy ghost object(C)#%d (1) { + ["b"]=> + uninitialized(int) + ["c"]=> + int(0) +} +Is lazy: 1 +# Proxy: +string(11) "initializer" +initializer exception +lazy proxy object(C)#%d (3) { + ["a"]=> + int(3) + ["b"]=> + int(4) + ["c"]=> + int(5) +} +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props.phpt new file mode 100644 index 0000000000000..ce94fc8b2ab79 --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props.phpt @@ -0,0 +1,79 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception (dynamic properties) +--FILE-- +setRawValueWithoutLazyInitialization($obj, 0); + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump($obj); + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $obj->d = 6; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $obj->d = 6; + throw new Exception('initializer exception'); +}); + +// Initializer effects on the proxy are not reverted +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +initializer exception +lazy ghost object(C)#%d (1) { + ["b"]=> + uninitialized(int) + ["c"]=> + int(0) +} +Is lazy: 1 +# Proxy: +string(11) "initializer" +initializer exception +lazy proxy object(C)#%d (4) { + ["a"]=> + int(3) + ["b"]=> + int(4) + ["c"]=> + int(5) + ["d"]=> + int(6) +} +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props_and_ht.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props_and_ht.phpt new file mode 100644 index 0000000000000..1bc3eb2cea8e1 --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_dyn_props_and_ht.phpt @@ -0,0 +1,86 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception (dynamic properties, initialized hashtable) +--FILE-- +setRawValueWithoutLazyInitialization($obj, 0); + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump($obj); + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $obj->d = 6; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $obj->d = 6; + throw new Exception('initializer exception'); +}); + +// Initializer effects on the proxy are not reverted +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +array(0) { +} +string(11) "initializer" +initializer exception +lazy ghost object(C)#%d (1) { + ["b"]=> + uninitialized(int) + ["c"]=> + int(0) +} +Is lazy: 1 +# Proxy: +array(0) { +} +string(11) "initializer" +initializer exception +lazy proxy object(C)#%d (4) { + ["a"]=> + int(3) + ["b"]=> + int(4) + ["c"]=> + int(5) + ["d"]=> + int(6) +} +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_nested.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_nested.phpt new file mode 100644 index 0000000000000..535f93e65f63f --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_nested.phpt @@ -0,0 +1,50 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception (nested) +--FILE-- +newLazyGhost(function ($c) { + $c->c = 1; +}); + +$b = $bReflector->newLazyGhost(function () { + throw new \Exception('xxx'); +}); + +$a = $aReflector->newLazyGhost(function ($a) use ($b, $c) { + $a->a = $c->c + $b->b; +}); + +try { + $a->init = 'please'; +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump($a, $b, $c); + +?> +--EXPECTF-- +Exception: xxx +lazy ghost object(A)#%d (0) { +} +lazy ghost object(B)#%d (0) { +} +object(C)#%d (1) { + ["c"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_overridden_prop.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_overridden_prop.phpt new file mode 100644 index 0000000000000..656d267e137fa --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_overridden_prop.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: Object is still lazy after initializer exception (overridden prop) +--FILE-- +getProperty('b')->skipLazyInitialization($obj); + $i = 5; + $obj->b = &$i; + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + throw new Exception('initializer exception'); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +string(11) "initializer" +initializer exception +Is lazy: 1 +# Proxy: +string(11) "initializer" +initializer exception +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht.phpt new file mode 100644 index 0000000000000..c4f0bd98773fd --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht.phpt @@ -0,0 +1,85 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception (properties hashtable) +--FILE-- +setRawValueWithoutLazyInitialization($obj, 0); + + // Builds properties hashtable + var_dump(get_mangled_object_vars($obj)); + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump($obj); + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + throw new Exception('initializer exception'); +}); + +// Initializer effects on the proxy are not reverted +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +array(1) { + ["c"]=> + int(0) +} +string(11) "initializer" +initializer exception +lazy ghost object(C)#%d (1) { + ["b"]=> + uninitialized(int) + ["c"]=> + int(0) +} +Is lazy: 1 +# Proxy: +array(1) { + ["c"]=> + int(0) +} +string(11) "initializer" +initializer exception +lazy proxy object(C)#%d (3) { + ["a"]=> + int(3) + ["b"]=> + int(4) + ["c"]=> + int(5) +} +Is lazy: 1 diff --git a/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht_ref.phpt b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht_ref.phpt new file mode 100644 index 0000000000000..094f5c9b80947 --- /dev/null +++ b/Zend/tests/lazy_objects/init_exception_reverts_initializer_changes_props_ht_ref.phpt @@ -0,0 +1,97 @@ +--TEST-- +Lazy objects: Initializer effects are reverted after exception (properties hashtable referenced after initializer) +--FILE-- +setRawValueWithoutLazyInitialization($obj, 0); + + // Builds properties hashtable + var_dump(get_mangled_object_vars($obj)); + + try { + $reflector->initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump($obj); + printf("Is lazy: %d\n", $reflector->isUninitializedLazyObject($obj)); + + var_dump($table); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + global $table; + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $table = (array) $obj; + throw new Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + global $table; + var_dump("initializer"); + $obj->a = 3; + $obj->b = 4; + $obj->c = 5; + $table = (array) $obj; + throw new Exception('initializer exception'); +}); + +// Initializer effects on the proxy are not reverted +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +array(1) { + ["c"]=> + int(0) +} +string(11) "initializer" +initializer exception +lazy ghost object(C)#%d (1) { + ["b"]=> + uninitialized(int) + ["c"]=> + int(0) +} +Is lazy: 1 + +Warning: Undefined variable $table in %s on line %d +NULL +# Proxy: +array(1) { + ["c"]=> + int(0) +} +string(11) "initializer" +initializer exception +lazy proxy object(C)#%d (3) { + ["a"]=> + int(3) + ["b"]=> + int(4) + ["c"]=> + int(5) +} +Is lazy: 1 + +Warning: Undefined variable $table in %s on line %d +NULL diff --git a/Zend/tests/lazy_objects/init_fatal.phpt b/Zend/tests/lazy_objects/init_fatal.phpt new file mode 100644 index 0000000000000..7f5e03c94b0fa --- /dev/null +++ b/Zend/tests/lazy_objects/init_fatal.phpt @@ -0,0 +1,35 @@ +--TEST-- +Lazy objects: fatal error during initialization of ghost object +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, new stdClass); + +var_dump($obj); +var_dump($obj->c); +var_dump($obj); + +--EXPECTF-- +lazy ghost object(C)#%d (1) { + ["b"]=> + object(stdClass)#%d (0) { + } +} +string(11) "initializer" + +Parse error: Unclosed '{' in %s on line %d diff --git a/Zend/tests/lazy_objects/init_handles_ref_source_types.phpt b/Zend/tests/lazy_objects/init_handles_ref_source_types.phpt new file mode 100644 index 0000000000000..366751e02d7bb --- /dev/null +++ b/Zend/tests/lazy_objects/init_handles_ref_source_types.phpt @@ -0,0 +1,100 @@ +--TEST-- +Lazy objects: Pre-initialization reference source types are properly handled (no initialization exception) +--FILE-- +a = null; + unset($this->b); + $this->b = null; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s:\n", $name); + + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, null); + $refA = &$obj->a; + $reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, null); + $refB = &$obj->b; + + var_dump($obj); + var_dump($obj->c); + var_dump($obj); + + try { + // $refA retained its reference source type (except for the proxy + // case: its the responsibility of the initializer to propagate + // pre-initialized properties to the instance) + $refA = 1; + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // source type was not duplicated + unset($obj->a); + $refA = 1; + + $refB = 1; +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(null); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +string(11) "initializer" +NULL +object(C)#%d (3) { + ["a"]=> + &NULL + ["b"]=> + NULL + ["c"]=> + NULL +} +TypeError: Cannot assign int to reference held by property C::$a of type ?C +# Proxy: +lazy proxy object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +string(11) "initializer" +NULL +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (3) { + ["a"]=> + NULL + ["b"]=> + NULL + ["c"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/init_handles_ref_source_types_exception.phpt b/Zend/tests/lazy_objects/init_handles_ref_source_types_exception.phpt new file mode 100644 index 0000000000000..0613ca5f3f44d --- /dev/null +++ b/Zend/tests/lazy_objects/init_handles_ref_source_types_exception.phpt @@ -0,0 +1,109 @@ +--TEST-- +Lazy objects: Pre-initialization reference source types are properly handled after initializer exception +--FILE-- +b); + throw new \Exception('initializer exception'); + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s:\n", $name); + + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, null); + $refA = &$obj->a; + $reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, null); + $refB = &$obj->b; + + var_dump($obj); + try { + var_dump($obj->c); + } catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + var_dump($obj); + + try { + // $refA retained its reference source type (except for the proxy + // case: it is the responsibility of the initializer to propagate + // pre-initialized properties to the instance) + $refA = 1; + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // source type was not duplicated + unset($obj->a); + $refA = 1; + + try { + // $refB retained its reference source type + $refB = 1; + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // source type was not duplicated + unset($obj->b); + $refB = 1; + +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(null); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +string(11) "initializer" +Exception: initializer exception +lazy ghost object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +TypeError: Cannot assign int to reference held by property C::$a of type ?C +TypeError: Cannot assign int to reference held by property C::$b of type ?C +# Proxy: +lazy proxy object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +string(11) "initializer" +Exception: initializer exception +lazy proxy object(C)#%d (2) { + ["a"]=> + &NULL + ["b"]=> + &NULL +} +TypeError: Cannot assign int to reference held by property C::$a of type ?C +TypeError: Cannot assign int to reference held by property C::$b of type ?C diff --git a/Zend/tests/lazy_objects/init_may_leave_props_uninit.phpt b/Zend/tests/lazy_objects/init_may_leave_props_uninit.phpt new file mode 100644 index 0000000000000..954433918b92b --- /dev/null +++ b/Zend/tests/lazy_objects/init_may_leave_props_uninit.phpt @@ -0,0 +1,46 @@ +--TEST-- +Lazy objects: properties with no default values are left uninitialized +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +try { + var_dump($obj->b); +} catch (Error $e) { + printf("%s\n", $e); +} +var_dump($obj); +--EXPECTF-- +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +NULL +Error: Typed property C::$b must not be accessed before initialization in %s:%d +Stack trace: +#0 {main} +object(C)#%d (1) { + ["a"]=> + NULL + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/init_preserves_identity.phpt b/Zend/tests/lazy_objects/init_preserves_identity.phpt new file mode 100644 index 0000000000000..7ef197d43112a --- /dev/null +++ b/Zend/tests/lazy_objects/init_preserves_identity.phpt @@ -0,0 +1,29 @@ +--TEST-- +Lazy objects: initialization of proxy does not change object id +--FILE-- +resetAsLazyProxy($object, function (MyObject $object) use (&$object2Id) { + $object2 = new MyObject(); + $object2Id = spl_object_id($object2); + return $object2; +}); +var_dump(spl_object_id($object) === $objectId); +$reflector->initializeLazyObject($object); +var_dump(spl_object_id($object) === $objectId); +var_dump(spl_object_id($object) !== $object2Id); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/Zend/tests/lazy_objects/init_preserves_proxy_class.phpt b/Zend/tests/lazy_objects/init_preserves_proxy_class.phpt new file mode 100644 index 0000000000000..913450ba66b0f --- /dev/null +++ b/Zend/tests/lazy_objects/init_preserves_proxy_class.phpt @@ -0,0 +1,33 @@ +--TEST-- +Lazy objects: initialization of proxy does not change the class of the object +--FILE-- +newLazyProxy(function (B $o) { + return new A(); +}); + +var_dump(get_class($o)); +$o->foo(); +$o->s = 'init'; +var_dump(get_class($o)); +$o->foo(); + + +?> +--EXPECT-- +string(1) "B" +string(6) "B::foo" +string(1) "B" +string(6) "B::foo" diff --git a/Zend/tests/lazy_objects/init_sets_prop_default_values.phpt b/Zend/tests/lazy_objects/init_sets_prop_default_values.phpt new file mode 100644 index 0000000000000..7c0494eee0236 --- /dev/null +++ b/Zend/tests/lazy_objects/init_sets_prop_default_values.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: props are initialized to default values before calling initializer +--FILE-- +a = 3; + $this->b = 4; + } +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + var_dump($obj); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); +--EXPECTF-- +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +string(14) "C::__construct" +int(3) +object(C)#%d (2) { + ["a"]=> + int(3) + ["b"]=> + int(4) +} diff --git a/Zend/tests/lazy_objects/init_trigger_array_cast.phpt b/Zend/tests/lazy_objects/init_trigger_array_cast.phpt new file mode 100644 index 0000000000000..d48633f33dfad --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_array_cast.phpt @@ -0,0 +1,58 @@ +--TEST-- +Lazy objects: array cast does not initialize object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump((array)$obj); + +$obj->a = 2; +var_dump((array)$obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump((array)$obj); + +$obj->a = 2; +var_dump((array)$obj); + +--EXPECTF-- +# Ghost: +array(0) { +} +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(2) +} +# Proxy: +array(0) { +} +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/init_trigger_compare.phpt b/Zend/tests/lazy_objects/init_trigger_compare.phpt new file mode 100644 index 0000000000000..bebd5e9c5f0c7 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_compare.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: comparison initializes object +--FILE-- +newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +$b = $reflector->newLazyProxy(function ($obj) { + return new C(); +}); + +var_dump($a > $b); + +$a = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +$b = $reflector->newLazyProxy(function ($obj) { + return new C(); +}); + +var_dump($a == $b); + +$a = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +var_dump('A' < $a); +?> +--EXPECT-- +string(14) "C::__construct" +string(14) "C::__construct" +bool(false) +string(14) "C::__construct" +string(14) "C::__construct" +bool(true) +bool(true) diff --git a/Zend/tests/lazy_objects/init_trigger_debug_zval_dump.phpt b/Zend/tests/lazy_objects/init_trigger_debug_zval_dump.phpt new file mode 100644 index 0000000000000..b755484c15d93 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_debug_zval_dump.phpt @@ -0,0 +1,63 @@ +--TEST-- +Lazy objects: debug_zval_dump does not initialize object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +debug_zval_dump($obj); +$reflector->initializeLazyObject($obj); +debug_zval_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +debug_zval_dump($obj); +$reflector->initializeLazyObject($obj); +debug_zval_dump($obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) refcount(2){ + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) refcount(2){ + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) refcount(2){ + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) refcount(2){ + ["instance"]=> + object(C)#%d (1) refcount(2){ + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/init_trigger_foreach.phpt b/Zend/tests/lazy_objects/init_trigger_foreach.phpt new file mode 100644 index 0000000000000..b8bd780666bb8 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_foreach.phpt @@ -0,0 +1,48 @@ +--TEST-- +Lazy objects: Foreach initializes object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +foreach ($obj as $prop => $value) { + var_dump($prop, $value); +} + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +foreach ($obj as $prop => $value) { + var_dump($prop, $value); +} + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) diff --git a/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt b/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt new file mode 100644 index 0000000000000..d955c92c5ab01 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt @@ -0,0 +1,96 @@ +--TEST-- +Lazy objects: Foreach initializes object +--FILE-- +b; } + set(int $value) { $this->b = $value; } + } + public int $c { + get { return $this->a + 2; } + } + public function __construct() { + var_dump(__METHOD__); + $this->a = 1; + $this->b = 2; + $this->d = 4; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +foreach ($obj as $prop => $value) { + var_dump($prop, $value); +} + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +foreach ($obj as $prop => $value) { + var_dump($prop, $value); +} + +print "# Ghost (init exception):\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception(); +}); + +try { + var_dump(json_encode($obj)); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +print "# Proxy (init exception):\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new \Exception(); +}); + +try { + var_dump(json_encode($obj)); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "c" +int(3) +string(1) "d" +int(4) +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "c" +int(3) +# Ghost (init exception): +Exception: +# Proxy (init exception): +Exception: diff --git a/Zend/tests/lazy_objects/init_trigger_get_mangled_object_vars.phpt b/Zend/tests/lazy_objects/init_trigger_get_mangled_object_vars.phpt new file mode 100644 index 0000000000000..aff038fcda25f --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_get_mangled_object_vars.phpt @@ -0,0 +1,56 @@ +--TEST-- +Lazy objects: get_mangled_object_vars does not initialize object +--FILE-- +a = 1; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump(get_mangled_object_vars($obj)); + + $obj->a = 2; + var_dump(get_mangled_object_vars($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +array(0) { +} +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(2) +} +# Proxy: +array(0) { +} +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/init_trigger_get_object_vars.phpt b/Zend/tests/lazy_objects/init_trigger_get_object_vars.phpt new file mode 100644 index 0000000000000..ca14328377488 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_get_object_vars.phpt @@ -0,0 +1,62 @@ +--TEST-- +Lazy objects: get_object_vars initializes object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump(get_object_vars($obj)); + +$obj->a = 2; +var_dump(get_object_vars($obj)); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump(get_object_vars($obj)); + +$obj->a = 2; +var_dump(get_object_vars($obj)); + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(1) +} +array(1) { + ["a"]=> + int(2) +} +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +array(1) { + ["a"]=> + int(1) +} +array(1) { + ["a"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/init_trigger_json_encode.phpt b/Zend/tests/lazy_objects/init_trigger_json_encode.phpt new file mode 100644 index 0000000000000..c7c7aed99f1ce --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_json_encode.phpt @@ -0,0 +1,42 @@ +--TEST-- +Lazy objects: json_encode initializes object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump(json_encode($obj)); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump(json_encode($obj)); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(7) "{"a":1}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(7) "{"a":1}" diff --git a/Zend/tests/lazy_objects/init_trigger_json_encode_hooks.phpt b/Zend/tests/lazy_objects/init_trigger_json_encode_hooks.phpt new file mode 100644 index 0000000000000..c754d030e4305 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_json_encode_hooks.phpt @@ -0,0 +1,80 @@ +--TEST-- +Lazy objects: json_encode initializes object +--FILE-- +b; } + set(int $value) { $this->b = $value; } + } + public int $c { + get { return $this->a + 2; } + } + public function __construct() { + var_dump(__METHOD__); + $this->a = 1; + $this->b = 2; + $this->d = 4; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump(json_encode($obj)); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump(json_encode($obj)); + +print "# Ghost (init exception):\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception(); +}); + +try { + var_dump(json_encode($obj)); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +print "# Proxy (init exception):\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new \Exception(); +}); + +try { + var_dump(json_encode($obj)); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(25) "{"a":1,"b":2,"c":3,"d":4}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(25) "{"a":1,"b":2,"c":3,"d":4}" +# Ghost (init exception): +Exception: +# Proxy (init exception): +Exception: diff --git a/Zend/tests/lazy_objects/init_trigger_reflection_object_toString.phpt b/Zend/tests/lazy_objects/init_trigger_reflection_object_toString.phpt new file mode 100644 index 0000000000000..7f0cc6e4ff543 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_reflection_object_toString.phpt @@ -0,0 +1,45 @@ +--TEST-- +Lazy objects: ReflectionObject::__toString() does not trigger initialization +--FILE-- +a = 1; + } +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + (new ReflectionObject($obj))->__toString(); + + printf("Initialized:\n"); + var_dump(!(new ReflectionClass($obj))->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost +Initialized: +bool(false) +# Proxy +Initialized: +bool(false) diff --git a/Zend/tests/lazy_objects/init_trigger_serialize.phpt b/Zend/tests/lazy_objects/init_trigger_serialize.phpt new file mode 100644 index 0000000000000..5a5e2f78f66b7 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_serialize.phpt @@ -0,0 +1,43 @@ +--TEST-- +Lazy objects: serialize initializes object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump(serialize($obj)); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump(serialize($obj)); + + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" diff --git a/Zend/tests/lazy_objects/init_trigger_var_dump.phpt b/Zend/tests/lazy_objects/init_trigger_var_dump.phpt new file mode 100644 index 0000000000000..17dbe62cae6eb --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_var_dump.phpt @@ -0,0 +1,63 @@ +--TEST-- +Lazy objects: var_dump does not initialize object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +$reflector->initializeLazyObject($obj); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump($obj); +$reflector->initializeLazyObject($obj); +var_dump($obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_001.phpt b/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_001.phpt new file mode 100644 index 0000000000000..0fbcaf826e305 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_001.phpt @@ -0,0 +1,57 @@ +--TEST-- +Lazy objects: var_dump may not initialize object with __debugInfo() method +--FILE-- +a = 1; + } + public function __debugInfo() { + return ['hello']; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s\n", $name); + + var_dump($obj); + printf("Initialized:\n"); + var_dump(!$reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost +lazy ghost object(C)#%d (1) { + [0]=> + string(5) "hello" +} +Initialized: +bool(false) +# Proxy +lazy proxy object(C)#%d (1) { + [0]=> + string(5) "hello" +} +Initialized: +bool(false) diff --git a/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_002.phpt b/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_002.phpt new file mode 100644 index 0000000000000..54ebf1a80aaa3 --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_var_dump_debug_info_002.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: var_dump may initialize object with __debugInfo() method +--FILE-- +a = 1; + } + public function __debugInfo() { + return [$this->a]; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + printf("# %s\n", $name); + + var_dump($obj); + printf("Initialized:\n"); + var_dump(!$reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + [0]=> + int(1) +} +Initialized: +bool(true) +# Proxy +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + [0]=> + int(1) +} +Initialized: +bool(true) diff --git a/Zend/tests/lazy_objects/init_trigger_var_export.phpt b/Zend/tests/lazy_objects/init_trigger_var_export.phpt new file mode 100644 index 0000000000000..55d45063646dd --- /dev/null +++ b/Zend/tests/lazy_objects/init_trigger_var_export.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: var_export initializes object +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_export($obj); +print "\n"; + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_export($obj); +print "\n"; +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +\C::__set_state(array( + 'a' => 1, +)) +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +\C::__set_state(array( + 'a' => 1, +)) diff --git a/Zend/tests/lazy_objects/initializeLazyObject.phpt b/Zend/tests/lazy_objects/initializeLazyObject.phpt new file mode 100644 index 0000000000000..b53858a76a207 --- /dev/null +++ b/Zend/tests/lazy_objects/initializeLazyObject.phpt @@ -0,0 +1,62 @@ +--TEST-- +Lazy objects: ReflectionClass::initializeLazyObject() +--FILE-- +isUninitializedLazyObject($obj)); + + var_dump($reflector?->initializeLazyObject($obj)); + + printf("Initialized:\n"); + var_dump(!$reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new C(); + $c->a = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +Initialized: +bool(false) +string(11) "initializer" +object(C)#%d (1) { + ["a"]=> + int(1) +} +Initialized: +bool(true) +# Proxy: +Initialized: +bool(false) +string(11) "initializer" +object(C)#%d (1) { + ["a"]=> + int(1) +} +Initialized: +bool(true) diff --git a/Zend/tests/lazy_objects/initializeLazyObject_error.phpt b/Zend/tests/lazy_objects/initializeLazyObject_error.phpt new file mode 100644 index 0000000000000..541d6a8c491f1 --- /dev/null +++ b/Zend/tests/lazy_objects/initializeLazyObject_error.phpt @@ -0,0 +1,51 @@ +--TEST-- +Lazy objects: ReflectionClass::initializeLazyObject() error +--FILE-- +isUninitializedLazyObject($obj)); + + try { + var_dump($reflector?->initializeLazyObject($obj)); + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + + var_dump(!$reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + throw new \Exception('initializer exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + throw new \Exception('initializer exception'); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +bool(false) +string(11) "initializer" +initializer exception +bool(false) +# Proxy: +bool(false) +string(11) "initializer" +initializer exception +bool(false) diff --git a/Zend/tests/lazy_objects/initializeLazyObject_noop_on_initialized_object.phpt b/Zend/tests/lazy_objects/initializeLazyObject_noop_on_initialized_object.phpt new file mode 100644 index 0000000000000..c424d2ecd396d --- /dev/null +++ b/Zend/tests/lazy_objects/initializeLazyObject_noop_on_initialized_object.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: ReflectionClass::initializeLazyObject() on an initialized object is a no-op +--FILE-- +a); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + var_dump($reflector?->initializeLazyObject($obj)); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new C(); + $c->a = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +int(1) +bool(true) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(true) +# Proxy: +string(11) "initializer" +int(1) +bool(true) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(true) diff --git a/Zend/tests/lazy_objects/initializer_must_return_the_right_type.phpt b/Zend/tests/lazy_objects/initializer_must_return_the_right_type.phpt new file mode 100644 index 0000000000000..7f796684e1bd8 --- /dev/null +++ b/Zend/tests/lazy_objects/initializer_must_return_the_right_type.phpt @@ -0,0 +1,164 @@ +--TEST-- +Lazy objects: initializer must return the right type +--FILE-- +b = 1; + } + public function __destruct() { + } +} + +class C extends B { +} + +class D extends C { + public int $b; // override +} + +class E extends B { + public function __destruct() { // override + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost initializer must return NULL or no value:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + return new stdClass; +}); + +var_dump($obj); +try { + var_dump($obj->a); +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} +var_dump($obj); + +print "# Proxy initializer must return an instance of a compatible class:\n"; +print "## Valid cases:\n"; + +$tests = [ + [C::class, new C()], + [C::class, new B()], + [D::class, new B()], +]; + +foreach ($tests as [$class, $instance]) { + $obj = (new ReflectionClass($class))->newLazyProxy(function ($obj) use ($instance) { + var_dump("initializer"); + $instance->b = 1; + return $instance; + }); + + printf("## %s vs %s\n", get_class($obj), is_object($instance) ? get_class($instance) : gettype($instance)); + var_dump($obj->b); + var_dump($obj); +} + +print "## Invalid cases:\n"; + +$tests = [ + [C::class, new stdClass], + [C::class, new DateTime()], + [C::class, null], + [C::class, new D()], + [E::class, new B()], +]; + +foreach ($tests as [$class, $instance]) { + $obj = (new ReflectionClass($class))->newLazyProxy(function ($obj) use ($instance) { + var_dump("initializer"); + return $instance; + }); + + try { + printf("## %s vs %s\n", get_class($obj), is_object($instance) ? get_class($instance) : gettype($instance)); + var_dump($obj->a); + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return $obj; +}); + +try { + printf("## %s vs itself\n", get_class($obj)); + var_dump($obj->a); +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECTF-- +# Ghost initializer must return NULL or no value: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +TypeError: Lazy object initializer must return NULL or no value +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +# Proxy initializer must return an instance of a compatible class: +## Valid cases: +## C vs C +string(11) "initializer" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(1) + } +} +## C vs B +string(11) "initializer" +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(B)#%d (1) { + ["b"]=> + int(1) + } +} +## D vs B +string(11) "initializer" +int(1) +lazy proxy object(D)#%d (1) { + ["instance"]=> + object(B)#%d (1) { + ["b"]=> + int(1) + } +} +## Invalid cases: +## C vs stdClass +string(11) "initializer" +TypeError: The real instance class stdClass is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods. +## C vs DateTime +string(11) "initializer" +TypeError: The real instance class DateTime is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods. +## C vs NULL +string(11) "initializer" +TypeError: Lazy proxy factory must return an instance of a class compatible with C, null returned +## C vs D +string(11) "initializer" +TypeError: The real instance class D is not compatible with the proxy class C. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods. +## E vs B +string(11) "initializer" +TypeError: The real instance class B is not compatible with the proxy class E. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods. +## C vs itself +string(11) "initializer" +Error: Lazy proxy factory must return a non-lazy object diff --git a/Zend/tests/lazy_objects/invalid_options.phpt b/Zend/tests/lazy_objects/invalid_options.phpt new file mode 100644 index 0000000000000..96c965ec4175d --- /dev/null +++ b/Zend/tests/lazy_objects/invalid_options.phpt @@ -0,0 +1,51 @@ +--TEST-- +Lazy objects: invalid options +--FILE-- +newLazyGhost(function ($obj) { }, -1); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +try { + $obj = $reflector->newLazyProxy(function ($obj) { }, -1); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +try { + // SKIP_DESTRUCTOR is only allowed on resetAsLazyProxy() + $obj = $reflector->newLazyGhost(function ($obj) { }, ReflectionClass::SKIP_DESTRUCTOR); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +$obj = new C(); + +try { + $reflector->resetAsLazyGhost($obj, function ($obj) { }, -1); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +try { + $reflector->resetAsLazyProxy($obj, function ($obj) { }, -1); +} catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +?> +--EXPECT-- +ReflectionException: ReflectionClass::newLazyGhost(): Argument #2 ($options) contains invalid flags +ReflectionException: ReflectionClass::newLazyProxy(): Argument #2 ($options) contains invalid flags +ReflectionException: ReflectionClass::newLazyGhost(): Argument #2 ($options) does not accept ReflectionClass::SKIP_DESTRUCTOR +ReflectionException: ReflectionClass::resetAsLazyGhost(): Argument #3 ($options) contains invalid flags +ReflectionException: ReflectionClass::resetAsLazyProxy(): Argument #3 ($options) contains invalid flags diff --git a/Zend/tests/lazy_objects/isUninitializedLazyObject.phpt b/Zend/tests/lazy_objects/isUninitializedLazyObject.phpt new file mode 100644 index 0000000000000..82989b78409be --- /dev/null +++ b/Zend/tests/lazy_objects/isUninitializedLazyObject.phpt @@ -0,0 +1,49 @@ +--TEST-- +Lazy objects: ReflectionClass::isUninitializedLazyObject() +--FILE-- +isUninitializedLazyObject($obj)); + var_dump($obj->a); + var_dump($reflector->isUninitializedLazyObject($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj = new C(); + $obj->a = 1; + return $obj; +}); + +test('Proxy', $obj); + +?> +--EXPECT-- +# Ghost +bool(true) +string(11) "initializer" +int(1) +bool(false) +# Proxy +bool(true) +string(11) "initializer" +int(1) +bool(false) diff --git a/Zend/tests/lazy_objects/isset_hooked_may_initialize.phpt b/Zend/tests/lazy_objects/isset_hooked_may_initialize.phpt new file mode 100644 index 0000000000000..3bdb63c3166b9 --- /dev/null +++ b/Zend/tests/lazy_objects/isset_hooked_may_initialize.phpt @@ -0,0 +1,75 @@ +--TEST-- +Lazy objects: hooked property isset initializes object if hook observes object state +--FILE-- +a; } + set($value) { $this->a = $value; } + } + public int $b = 1; + + public function __construct(int $a) { + var_dump(__METHOD__); + $this->a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump(isset($obj->a)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +bool(true) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/isset_hooked_may_not_initialize.phpt b/Zend/tests/lazy_objects/isset_hooked_may_not_initialize.phpt new file mode 100644 index 0000000000000..35f15be1007c3 --- /dev/null +++ b/Zend/tests/lazy_objects/isset_hooked_may_not_initialize.phpt @@ -0,0 +1,64 @@ +--TEST-- +Lazy objects: hooked property isset may does not initialize object if hook does not observe object state +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump(isset($obj->a)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +bool(true) +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +bool(true) +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/isset_initializes.phpt b/Zend/tests/lazy_objects/isset_initializes.phpt new file mode 100644 index 0000000000000..1478bb054fce2 --- /dev/null +++ b/Zend/tests/lazy_objects/isset_initializes.phpt @@ -0,0 +1,72 @@ +--TEST-- +Lazy objects: property isset initializes object +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + var_dump(isset($obj->a)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +bool(true) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_dynamic.phpt b/Zend/tests/lazy_objects/jit_assign_obj_dynamic.phpt new file mode 100644 index 0000000000000..d49fb585cecc9 --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_dynamic.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ with dynamic prop +--FILE-- +b = 3; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a = 2; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (2) { + ["b"]=> + int(3) + ["a"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["b"]=> + int(3) + ["a"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_op_dynamic.phpt b/Zend/tests/lazy_objects/jit_assign_obj_op_dynamic.phpt new file mode 100644 index 0000000000000..cdf639815e3bb --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_op_dynamic.phpt @@ -0,0 +1,70 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ_OP with dynamic prop +--FILE-- +a = 1; + $this->b = 3; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a += 1; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (2) { + ["b"]=> + int(1) + ["a"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["b"]=> + int(3) + ["a"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_op_prop_info.phpt b/Zend/tests/lazy_objects/jit_assign_obj_op_prop_info.phpt new file mode 100644 index 0000000000000..31689723c202c --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_op_prop_info.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ_OP with known prop_info +--FILE-- +b = 3; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a += 1; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info.phpt b/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info.phpt new file mode 100644 index 0000000000000..7c812514eb2e9 --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ_OP with unknown prop info +--FILE-- +a = 1; + $this->b = 2; + } + function test(object $obj) { + $obj->a += 1; + } +} + +$reflector = new ReflectionClass(C::class); + +for ($i = 0; $i < 2; $i++) { + $obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); + // Call via reflection to avoid inlining. + // - test() handlers are executed once, and prime the runtime cache + // - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path + $reflector->getMethod('test')->invoke($obj, $obj); + var_dump($obj); +} + +--EXPECTF-- +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(2) + ["b"]=> + int(2) +} +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(2) + ["b"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info_untyped.phpt b/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info_untyped.phpt new file mode 100644 index 0000000000000..211090110245a --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_op_unknown_prop_info_untyped.phpt @@ -0,0 +1,48 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ_OP with unknown prop info untyped +--FILE-- +a = 1; + $this->b = 2; + } + function test(object $obj) { + $obj->a += 1; + } +} + +$reflector = new ReflectionClass(C::class); + +for ($i = 0; $i < 2; $i++) { + $obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); + // Call via reflection to avoid inlining. + // - test() handlers are executed once, and prime the runtime cache + // - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path + $reflector->getMethod('test')->invoke($obj, $obj); + var_dump($obj); +} + +?> +--EXPECTF-- +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(2) + ["b"]=> + int(2) +} +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(2) + ["b"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_prop_info.phpt b/Zend/tests/lazy_objects/jit_assign_obj_prop_info.phpt new file mode 100644 index 0000000000000..631ba647e8859 --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_prop_info.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ with known prop_info +--FILE-- +b = 3; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a = 2; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info.phpt b/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info.phpt new file mode 100644 index 0000000000000..6d48cbd2861c1 --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info.phpt @@ -0,0 +1,46 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ with unknown prop info +--FILE-- +b = 2; + } + function test(object $obj) { + $obj->a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +for ($i = 0; $i < 2; $i++) { + $obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); + // Call via reflection to avoid inlining. + // - test() handlers are executed once, and prime the runtime cache + // - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path + $reflector->getMethod('test')->invoke($obj, $obj); + var_dump($obj); +} + +--EXPECTF-- +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(1) + ["b"]=> + int(2) +} +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(1) + ["b"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info_untyped.phpt b/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info_untyped.phpt new file mode 100644 index 0000000000000..f3c2d7dbe722a --- /dev/null +++ b/Zend/tests/lazy_objects/jit_assign_obj_unknown_prop_info_untyped.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: JIT: ASSIGN_OBJ with unknown prop info untyped +--FILE-- +b = 2; + } + function test(object $obj) { + $obj->a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +for ($i = 0; $i < 2; $i++) { + $obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); + // Call via reflection to avoid inlining. + // - test() handlers are executed once, and prime the runtime cache + // - On subsequent calls, the JIT'ed code is used, and we enter the valid runtime cache path + $reflector->getMethod('test')->invoke($obj, $obj); + var_dump($obj); +} + +?> +--EXPECTF-- +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(1) + ["b"]=> + int(2) +} +string(11) "initializer" +object(C)#%d (2) { + ["a":"C":private]=> + int(1) + ["b"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/json_encode_dynamic_props.phpt b/Zend/tests/lazy_objects/json_encode_dynamic_props.phpt new file mode 100644 index 0000000000000..19b4e7c86005a --- /dev/null +++ b/Zend/tests/lazy_objects/json_encode_dynamic_props.phpt @@ -0,0 +1,49 @@ +--TEST-- +Lazy objects: json_encode with dynamic props on initialized object +--FILE-- +newLazyGhost(function ($obj) { + $obj->dyn = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + $c = new C(); + $c->dyn = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost +object(stdClass)#%d (2) { + ["a"]=> + int(1) + ["dyn"]=> + int(1) +} +# Proxy +object(stdClass)#%d (2) { + ["a"]=> + int(1) + ["dyn"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/markLazyObjectAsInitialized.phpt b/Zend/tests/lazy_objects/markLazyObjectAsInitialized.phpt new file mode 100644 index 0000000000000..691cba7f34f4a --- /dev/null +++ b/Zend/tests/lazy_objects/markLazyObjectAsInitialized.phpt @@ -0,0 +1,66 @@ +--TEST-- +Lazy objects: markLazyObjectAsInitialized() initializes properties to their default value and skips initializer +--FILE-- +isUninitializedLazyObject($obj)); + + printf("markLazyObjectAsInitialized(true) returns \$obj:\n"); + var_dump($reflector?->markLazyObjectAsInitialized($obj) === $obj); + + printf("Initialized:\n"); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new C(); + $c->a = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +Initialized: +bool(false) +markLazyObjectAsInitialized(true) returns $obj: +bool(true) +Initialized: +bool(true) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +Initialized: +bool(false) +markLazyObjectAsInitialized(true) returns $obj: +bool(true) +Initialized: +bool(true) +object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/realize.phpt b/Zend/tests/lazy_objects/realize.phpt new file mode 100644 index 0000000000000..988b8114bde6f --- /dev/null +++ b/Zend/tests/lazy_objects/realize.phpt @@ -0,0 +1,96 @@ +--TEST-- +Lazy objects: Object is not lazy anymore if all props have been assigned a value +--FILE-- +b = 'b'; + } +} + +#[AllowDynamicProperties] +class C extends B { + public string $a; + + public function __construct() { + parent::__construct(); + $this->a = 'a'; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s:\n", $name); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a1'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + // Should not count a second prop initialization + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a2'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + try { + // Should not count a prop initialization + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // Should not count a prop initialization + //$reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, 'dynamic B'); + //var_dump(!$reflector->isUninitializedLazyObject($obj)); + + (new ReflectionProperty(B::class, 'b'))->setRawValueWithoutLazyInitialization($obj, 'b'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +bool(false) +bool(false) +bool(false) +TypeError: Cannot assign stdClass to property C::$a of type string +bool(true) +object(C)#%d (2) { + ["b":"B":private]=> + string(1) "b" + ["a"]=> + string(2) "a2" +} +# Proxy: +bool(false) +bool(false) +bool(false) +TypeError: Cannot assign stdClass to property C::$a of type string +bool(true) +object(C)#%d (2) { + ["b":"B":private]=> + string(1) "b" + ["a"]=> + string(2) "a2" +} diff --git a/Zend/tests/lazy_objects/realize_no_props.phpt b/Zend/tests/lazy_objects/realize_no_props.phpt new file mode 100644 index 0000000000000..0344c560c8a65 --- /dev/null +++ b/Zend/tests/lazy_objects/realize_no_props.phpt @@ -0,0 +1,78 @@ +--TEST-- +Lazy objects: Object with no props is never lazy +--FILE-- +isUninitializedLazyObject($obj)); + var_dump($obj); + + var_dump((new ReflectionClass($obj2::class))->isUninitializedLazyObject($obj2)); + var_dump($obj2); + + var_dump((new ReflectionClass($obj3::class))->isUninitializedLazyObject($obj3)); + var_dump($obj3); +} + +$obj = new C(); +(new ReflectionClass($obj))->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); +}); + +$obj2 = new D(); +$obj2->dynamic = 'value'; +(new ReflectionClass($obj2))->resetAsLazyGhost($obj2, function ($obj2) { + var_dump("initializer"); +}); + +$obj3 = (new ReflectionClass(C::class))->newLazyGhost(function () { + var_dump("initializer"); +}); + +test('Ghost', $obj, $obj2, $obj3); + +$obj = new C(); +(new ReflectionClass($obj))->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); +}); + +$obj2 = new D(); +$obj2->dynamic = 'value'; +(new ReflectionClass($obj2))->resetAsLazyProxy($obj2, function ($obj2) { + var_dump("initializer"); +}); + +$obj3 = (new ReflectionClass(C::class))->newLazyGhost(function () { + var_dump("initializer"); +}); + +test('Proxy', $obj, $obj2, $obj3); + +--EXPECTF-- +# Ghost: +bool(false) +object(C)#%d (0) { +} +bool(false) +object(D)#%d (0) { +} +bool(false) +object(C)#%d (0) { +} +# Proxy: +bool(false) +object(C)#%d (0) { +} +bool(false) +object(D)#%d (0) { +} +bool(false) +object(C)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/realize_proxy_overridden.phpt b/Zend/tests/lazy_objects/realize_proxy_overridden.phpt new file mode 100644 index 0000000000000..7443c7f0a66a3 --- /dev/null +++ b/Zend/tests/lazy_objects/realize_proxy_overridden.phpt @@ -0,0 +1,91 @@ +--TEST-- +Lazy objects: Object is not lazy anymore if all props have been assigned a value (overridden prop) +--FILE-- +b = 'b'; + } +} + +class C extends B { + public string $a; + public readonly string $b; + + public function __construct() { + parent::__construct(); + $this->a = 'a'; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s:\n", $name); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a1'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + // Should not count a second prop initialization + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'a2'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + try { + // Should not count a prop initialization + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + (new ReflectionProperty(B::class, 'b'))->setRawValueWithoutLazyInitialization($obj, 'b'); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +bool(false) +bool(false) +bool(false) +TypeError: Cannot assign stdClass to property C::$a of type string +bool(true) +object(C)#%d (2) { + ["b"]=> + string(1) "b" + ["a"]=> + string(2) "a2" +} +# Proxy: +bool(false) +bool(false) +bool(false) +TypeError: Cannot assign stdClass to property C::$a of type string +bool(true) +object(C)#%d (2) { + ["b"]=> + string(1) "b" + ["a"]=> + string(2) "a2" +} diff --git a/Zend/tests/lazy_objects/realize_skipped.phpt b/Zend/tests/lazy_objects/realize_skipped.phpt new file mode 100644 index 0000000000000..711ccfa93c000 --- /dev/null +++ b/Zend/tests/lazy_objects/realize_skipped.phpt @@ -0,0 +1,101 @@ +--TEST-- +Lazy objects: Object is not lazy anymore if all props have been skipped +--FILE-- +b = 'b'; + } +} + +#[AllowDynamicProperties] +class C extends B { + public string $a; + + public function __construct() { + parent::__construct(); + $this->a = 'a'; + } +} + +function test(string $name, object $obj) { + $reflector = new ReflectionClass(C::class); + + printf("# %s:\n", $name); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + $reflector->getProperty('a')->skipLazyInitialization($obj); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + // Should not count a second prop initialization + $reflector->getProperty('a')->skipLazyInitialization($obj); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + try { + // Should not count a prop initialization + $reflector->getProperty('xxx')->skipLazyInitialization($obj); + } catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + try { + // Should not count a prop initialization + $reflector->getProperty('b')->skipLazyInitialization($obj); + } catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + (new ReflectionProperty(B::class, 'b'))->skipLazyInitialization($obj); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +bool(false) +bool(false) +bool(false) +ReflectionException: Property C::$xxx does not exist +ReflectionException: Property C::$b does not exist +bool(true) +object(C)#%d (0) { + ["b":"B":private]=> + uninitialized(string) + ["a"]=> + uninitialized(string) +} +# Proxy: +bool(false) +bool(false) +bool(false) +ReflectionException: Property C::$xxx does not exist +ReflectionException: Property C::$b does not exist +bool(true) +object(C)#%d (0) { + ["b":"B":private]=> + uninitialized(string) + ["a"]=> + uninitialized(string) +} diff --git a/Zend/tests/lazy_objects/reset_as_lazy_accepts_sub_classes.phpt b/Zend/tests/lazy_objects/reset_as_lazy_accepts_sub_classes.phpt new file mode 100644 index 0000000000000..b836fbfea58d7 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_accepts_sub_classes.phpt @@ -0,0 +1,28 @@ +--TEST-- +Lazy objects: resetAsLazy*() accept a sub-class of the reflected class +--FILE-- +resetAsLazyGhost(new A(), function () {}); +$reflector->resetAsLazyGhost(new B(), function () {}); + +try { + $reflector->resetAsLazyGhost(new C(), function () {}); +} catch (TypeError $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +?> +==DONE== +--EXPECT-- +TypeError: ReflectionClass::resetAsLazyGhost(): Argument #1 ($object) must be of type A, C given +==DONE== diff --git a/Zend/tests/lazy_objects/reset_as_lazy_already_exception.phpt b/Zend/tests/lazy_objects/reset_as_lazy_already_exception.phpt new file mode 100644 index 0000000000000..708855d312609 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_already_exception.phpt @@ -0,0 +1,56 @@ +--TEST-- +Lazy objects: resetAsLazy*() on already lazy object is not allowed +--FILE-- +resetAsLazyGhost($obj, function () {}); + +try { + $reflector->resetAsLazyGhost($obj, function ($obj) { + }); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +printf("# Proxy:\n"); + +$obj = new C(); +$reflector->resetAsLazyProxy($obj, function () {}); + +try { + $reflector->resetAsLazyProxy($obj, function ($obj) { + }); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +$obj = new C(); +$reflector->resetAsLazyProxy($obj, function () { + return new C(); +}); +$reflector->initializeLazyObject($obj); + +try { + $reflector->resetAsLazyProxy($obj, function ($obj) { + }); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +?> +==DONE== +--EXPECT-- +# Ghost: +ReflectionException: Object is already lazy +# Proxy: +ReflectionException: Object is already lazy +==DONE== diff --git a/Zend/tests/lazy_objects/reset_as_lazy_calls_destructor.phpt b/Zend/tests/lazy_objects/reset_as_lazy_calls_destructor.phpt new file mode 100644 index 0000000000000..733da55a5cbc5 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_calls_destructor.phpt @@ -0,0 +1,61 @@ +--TEST-- +Lazy objects: resetAsLazy*() calls destructor of pre-existing object +--FILE-- +a = 1; + } + + public function __destruct() { + var_dump(__METHOD__); + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = new C(); +print "In makeLazy\n"; +$reflector->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); +print "After makeLazy\n"; + +var_dump($obj->a); +$obj = null; + +print "# Proxy:\n"; + +$obj = new C(); +print "In makeLazy\n"; +$reflector->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); + return new C(); +}); +print "After makeLazy\n"; + +var_dump($obj->a); +$obj = null; + +?> +--EXPECT-- +# Ghost: +In makeLazy +string(13) "C::__destruct" +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" +# Proxy: +In makeLazy +string(13) "C::__destruct" +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" diff --git a/Zend/tests/lazy_objects/reset_as_lazy_can_reset_initialized_proxies.phpt b/Zend/tests/lazy_objects/reset_as_lazy_can_reset_initialized_proxies.phpt new file mode 100644 index 0000000000000..0ac167724c7da --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_can_reset_initialized_proxies.phpt @@ -0,0 +1,55 @@ +--TEST-- +Lazy objects: resetAsLazyProxy() can reset initialized proxies +--FILE-- +newLazyProxy(function ($proxy) { + return new A(1, $proxy); +}); + +print "Init\n"; + +$reflector->initializeLazyObject($proxy); + +var_dump($proxy); + +print "Reset\n"; + +$proxy = $reflector->resetAsLazyProxy($proxy, function () { + return new A(2); +}); + +?> +--EXPECTF-- +Init +lazy proxy object(A)#%d (1) { + ["instance"]=> + object(A)#%d (2) { + ["a"]=> + int(1) + ["proxy"]=> + *RECURSION* + } +} +Reset +string(13) "A::__destruct" +object(A)#%d (2) { + ["a"]=> + int(1) + ["proxy"]=> + object(A)#%d (0) { + } +} diff --git a/Zend/tests/lazy_objects/reset_as_lazy_deletes_reference_source_type.phpt b/Zend/tests/lazy_objects/reset_as_lazy_deletes_reference_source_type.phpt new file mode 100644 index 0000000000000..583d5ccc881e5 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_deletes_reference_source_type.phpt @@ -0,0 +1,83 @@ +--TEST-- +Lazy objects: resetAsLazy deletes reference source type +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = new C(); +$ref = &$obj->a; +try { + $ref = 'string'; +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} +$reflector->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +$ref = 'string'; +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = new C(); +$ref = &$obj->a; +try { + $ref = 'string'; +} catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} +$reflector->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); + return new C(); +}); + +$ret = 'string'; +var_dump($obj); +var_dump($obj->a); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# Ghost: +TypeError: Cannot assign string to reference held by property C::$a of type int +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +TypeError: Cannot assign string to reference held by property C::$a of type int +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/reset_as_lazy_destructor_exception.phpt b/Zend/tests/lazy_objects/reset_as_lazy_destructor_exception.phpt new file mode 100644 index 0000000000000..67a79ab6e8ac9 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_destructor_exception.phpt @@ -0,0 +1,57 @@ +--TEST-- +Lazy objects: Destructor exception in resetAsLazy*() +--FILE-- +a = 1; + } + + public function __destruct() { + throw new \Exception(__METHOD__); + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = new C(); +try { + $reflector->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +// Object was not made lazy +var_dump(!$reflector->isUninitializedLazyObject($obj)); + +print "# Proxy:\n"; + +$obj = new C(); +try { + (new ReflectionClass($obj))->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); + return new C(); + }); +} catch (\Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +// Object was not made lazy +var_dump(!(new ReflectionClass($obj))->isUninitializedLazyObject($obj)); + +?> +--EXPECT-- +# Ghost: +Exception: C::__destruct +bool(true) +# Proxy: +Exception: C::__destruct +bool(true) diff --git a/Zend/tests/lazy_objects/reset_as_lazy_ignores_additional_props.phpt b/Zend/tests/lazy_objects/reset_as_lazy_ignores_additional_props.phpt new file mode 100644 index 0000000000000..0aa542474a9fd --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_ignores_additional_props.phpt @@ -0,0 +1,103 @@ +--TEST-- +Lazy objects: resetAsLazy() ignores additional props +--FILE-- +d = 1; + } + } +} + +$reflector = new ReflectionClass(A::class); + +printf("# B\n"); + +$obj = new B(); +$obj->a = 1; +$obj->b = 2; +$reflector->resetAsLazyGhost($obj, function () {}); +var_dump($obj->b); +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +printf("# C\n"); + +$obj = new C(); +$obj->a = 1; +$obj->c = 2; +$reflector->resetAsLazyGhost($obj, function () {}); +var_dump($obj->c); +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +printf("# D\n"); + +$obj = new D(); +$obj->a = 1; +$reflector->resetAsLazyGhost($obj, function ($obj) { + $obj->__construct(true); +}); +var_dump($obj->d ?? 'undef'); +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# B +int(2) +lazy ghost object(B)#%d (1) { + ["b"]=> + int(2) +} +NULL +object(B)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(2) +} +# C +int(2) +lazy ghost object(C)#%d (1) { + ["c"]=> + int(2) +} +NULL +object(C)#%d (2) { + ["a"]=> + NULL + ["c"]=> + int(2) +} +# D +string(5) "undef" +lazy ghost object(D)#%d (0) { + ["d"]=> + uninitialized(int) +} +NULL +object(D)#%d (2) { + ["a"]=> + NULL + ["d"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/reset_as_lazy_initialized_proxy.phpt b/Zend/tests/lazy_objects/reset_as_lazy_initialized_proxy.phpt new file mode 100644 index 0000000000000..d277a4e247d6c --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_initialized_proxy.phpt @@ -0,0 +1,53 @@ +--TEST-- +Lazy objects: resetAsLazy*() can reset initialized proxy +--FILE-- +resetAsLazyProxy($obj, function () { + return new Obj('obj2'); +}); +$r->initializeLazyObject($obj); +var_dump($obj); +$r->resetAsLazyProxy($obj, function () { + return new Obj('obj3'); +}); +var_dump($obj); +$r->initializeLazyObject($obj); +var_dump($obj); + +?> +==DONE== +--EXPECTF-- +object(Obj)#%d (1) { + ["name"]=> + string(4) "obj1" +} +lazy proxy object(Obj)#%d (1) { + ["instance"]=> + object(Obj)#%d (1) { + ["name"]=> + string(4) "obj2" + } +} +lazy proxy object(Obj)#%d (0) { + ["name"]=> + uninitialized(string) +} +lazy proxy object(Obj)#%d (1) { + ["instance"]=> + object(Obj)#%d (1) { + ["name"]=> + string(4) "obj3" + } +} +==DONE== diff --git a/Zend/tests/lazy_objects/reset_as_lazy_may_call_nested_destructors.phpt b/Zend/tests/lazy_objects/reset_as_lazy_may_call_nested_destructors.phpt new file mode 100644 index 0000000000000..39f8683d54e50 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_may_call_nested_destructors.phpt @@ -0,0 +1,49 @@ +--TEST-- +Lazy objects: resetAsLazy*() may call destructors of reset properties +--FILE-- +foo = new Foo(); + +$reflector = new ReflectionClass(Bar::class); + +print "Reset\n"; + +$reflector->resetAsLazyProxy($bar, function (Bar $bar) { + $result = new Bar(); + $result->foo = null; + $result->s = 'init'; + return $result; +}); + +print "Dump\n"; + +var_dump($bar->s); + +print "Done\n"; + +?> +--EXPECT-- +Reset +Bar::__destruct +Foo::__destruct +Dump +string(4) "init" +Done +Bar::__destruct diff --git a/Zend/tests/lazy_objects/reset_as_lazy_may_skip_destructor.phpt b/Zend/tests/lazy_objects/reset_as_lazy_may_skip_destructor.phpt new file mode 100644 index 0000000000000..d0c22a0333ff2 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_may_skip_destructor.phpt @@ -0,0 +1,59 @@ +--TEST-- +Lazy objects: resetAsLazy*() calls destructor of pre-existing object, unless SKIP_DESTRUCTOR flag is used +--FILE-- +a = 1; + } + + public function __destruct() { + var_dump(__METHOD__); + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = new C(); +print "In makeLazy\n"; +$reflector->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}, ReflectionClass::SKIP_DESTRUCTOR); +print "After makeLazy\n"; + +var_dump($obj->a); +$obj = null; + +print "# Proxy:\n"; + +$obj = new C(); +print "In makeLazy\n"; +$reflector->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); + return new C(); +}, ReflectionClass::SKIP_DESTRUCTOR); +print "After makeLazy\n"; + +var_dump($obj->a); +$obj = null; + +?> +--EXPECT-- +# Ghost: +In makeLazy +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" +# Proxy: +In makeLazy +After makeLazy +string(11) "initializer" +int(1) +string(13) "C::__destruct" diff --git a/Zend/tests/lazy_objects/reset_as_lazy_readonly.phpt b/Zend/tests/lazy_objects/reset_as_lazy_readonly.phpt new file mode 100644 index 0000000000000..128e825e539a7 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_readonly.phpt @@ -0,0 +1,71 @@ +--TEST-- +Lazy objects: resetAsLazy*() preserves readonly semantics +--FILE-- +a = $value; + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + if ($setB) { + $this->b = $value; + } + try { + $this->c = $value; + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + } +} + +final class C extends B { +} + + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass($obj::class); + + $reflector->resetAsLazyGhost($obj, function ($obj) { + $obj->__construct(2, setB: true); + }); + + $reflector->initializeLazyObject($obj); + + var_dump($obj); +} + +$obj = new B(1); +test('B', $obj); + +$obj = new C(1); +test('C', $obj); + +--EXPECTF-- +# B +object(B)#%d (3) { + ["a"]=> + int(2) + ["b"]=> + int(2) + ["c"]=> + int(2) +} +# C +Error: Cannot modify readonly property B::$a +object(C)#%d (3) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["c"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/reset_as_lazy_real_instance.phpt b/Zend/tests/lazy_objects/reset_as_lazy_real_instance.phpt new file mode 100644 index 0000000000000..8bfc582d2c066 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_real_instance.phpt @@ -0,0 +1,29 @@ +--TEST-- +Lazy objects: resetAsLazy*() can make a real instance lazy +--FILE-- +resetAsLazyProxy($obj1, function () use (&$obj2) { + $obj2 = new Obj('obj2'); + return $obj2; +}); +$r->initializeLazyObject($obj1); +$r->resetAsLazyProxy($obj2, function () { + return new Obj('obj3'); +}); +var_dump($obj1->name); + +?> +==DONE== +--EXPECT-- +string(4) "obj3" +==DONE== diff --git a/Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt b/Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt new file mode 100644 index 0000000000000..d99a30b3c1164 --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt @@ -0,0 +1,82 @@ +--TEST-- +Lazy objects: resetAsLazy resets dynamic props +--FILE-- +a = new Canary(); + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = new C(); +$reflector->resetAsLazyGhost($obj, function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = new C(); +$reflector->resetAsLazyProxy($obj, function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# Ghost: +string(18) "Canary::__destruct" +lazy ghost object(C)#%d (0) { +} +string(11) "initializer" +object(Canary)#%d (0) { +} +object(C)#%d (2) { + ["b"]=> + NULL + ["a"]=> + object(Canary)#%d (0) { + } +} +# Proxy: +string(18) "Canary::__destruct" +string(18) "Canary::__destruct" +lazy proxy object(C)#%d (0) { +} +string(11) "initializer" +object(Canary)#%d (0) { +} +object(Canary)#%d (0) { +} +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["b"]=> + NULL + ["a"]=> + object(Canary)#%d (0) { + } + } +} +string(18) "Canary::__destruct" diff --git a/Zend/tests/lazy_objects/reset_as_lazy_while_init_exception.phpt b/Zend/tests/lazy_objects/reset_as_lazy_while_init_exception.phpt new file mode 100644 index 0000000000000..756bad70621da --- /dev/null +++ b/Zend/tests/lazy_objects/reset_as_lazy_while_init_exception.phpt @@ -0,0 +1,57 @@ +--TEST-- +Lazy objects: resetAsLazy*() on object being initialized +--FILE-- +resetAsLazyGhost($obj, function ($obj) use ($reflector) { + try { + $reflector->resetAsLazyGhost($obj, function () { }); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + try { + $reflector->resetAsLazyProxy($obj, function () { }); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + +}); +$reflector->initializeLazyObject($obj); + +printf("# Proxy:\n"); + +$obj = new C(); +$reflector->resetAsLazyProxy($obj, function ($obj) use ($reflector) { + try { + $reflector->resetAsLazyProxy($obj, function () { }); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + try { + $reflector->resetAsLazyGhost($obj, function () { }); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + return new C(); +}); +$reflector->initializeLazyObject($obj); + +?> +==DONE== +--EXPECT-- +# Ghost: +Error: Can not reset an object while it is being initialized +Error: Can not reset an object while it is being initialized +# Proxy: +Error: Can not reset an object while it is being initialized +Error: Can not reset an object while it is being initialized +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_001.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_001.phpt new file mode 100644 index 0000000000000..187aa61540cb1 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +Lazy objects: RFC example 001 +--FILE-- +foo; + } +} + +$initializer = static function (MyClass $ghost): void { + $ghost->__construct(123); +}; + +$reflector = new ReflectionClass(MyClass::class); +$object = $reflector->newLazyGhost($initializer); + +var_dump($object); +var_dump($object->getFoo()); +var_dump($object); + +?> +--EXPECTF-- +lazy ghost object(MyClass)#%d (0) { + ["foo":"MyClass":private]=> + uninitialized(int) +} +int(123) +object(MyClass)#%d (1) { + ["foo":"MyClass":private]=> + int(123) +} diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_002.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_002.phpt new file mode 100644 index 0000000000000..e86a25df88718 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_002.phpt @@ -0,0 +1,44 @@ +--TEST-- +Lazy objects: RFC example 002 +--FILE-- +foo; + } +} + +$reflector = new ReflectionClass(MyClass::class); + +$initializer = static function (MyClass $proxy): MyClass { + return new MyClass(123); +}; + +$object = $reflector->newLazyProxy($initializer); + +var_dump($object); +var_dump($object->getFoo()); +var_dump($object); + +?> +--EXPECTF-- +lazy proxy object(MyClass)#%d (0) { + ["foo":"MyClass":private]=> + uninitialized(int) +} +int(123) +lazy proxy object(MyClass)#%d (1) { + ["instance"]=> + object(MyClass)#%d (1) { + ["foo":"MyClass":private]=> + int(123) + } +} diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_003.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_003.phpt new file mode 100644 index 0000000000000..0463b745d4eed --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_003.phpt @@ -0,0 +1,51 @@ +--TEST-- +Lazy objects: RFC example 003 +--FILE-- +resetAsLazyGhost($this, $this->initialize(...), ReflectionClass::SKIP_DESTRUCTOR); + } + + public function initialize() + { + $this->foo = 123; + } + + public function getFoo() + { + return $this->foo; + } + + public function __destruct() + { + var_dump(__METHOD__); + } +} + +$object = new MyLazyClass(); + +var_dump($object); +var_dump($object->getFoo()); +var_dump($object); + +?> +==DONE== +--EXPECTF-- +lazy ghost object(MyLazyClass)#%d (0) { + ["foo":"MyLazyClass":private]=> + uninitialized(int) +} +int(123) +object(MyLazyClass)#%d (1) { + ["foo":"MyLazyClass":private]=> + int(123) +} +==DONE== +string(23) "MyLazyClass::__destruct" diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_004.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_004.phpt new file mode 100644 index 0000000000000..f2459d9aa6383 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_004.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: RFC example 004 +--FILE-- + 'Title', + 'content' => 'Content', + ]; +} + +$reflector = new ReflectionClass(BlogPost::class); +// Callable that retrieves the title and content from the database. +$initializer = function ($blogPost) { + var_dump("initialization"); + $data = loadFromDB(); + $blogPost->title = $data['title']; + $blogPost->content = $data['content']; +}; +$post = $reflector->newLazyGhost($initializer); + +// Without this line, the following call to ReflectionProperty::setValue() would trigger initialization. +$reflector->getProperty('id')->skipLazyInitialization($post); +$reflector->getProperty('id')->setValue($post, 123); + +// Alternatively, one can use this directly: +$reflector->getProperty('id')->setRawValueWithoutLazyInitialization($post, 123); + +var_dump($post); +var_dump($post->id); +var_dump($post->title); +var_dump($post->content); +var_dump($post); + +?> +==DONE== +--EXPECTF-- +lazy ghost object(BlogPost)#%d (1) { + ["id"]=> + int(123) + ["title"]=> + uninitialized(string) + ["content"]=> + uninitialized(string) +} +int(123) +string(14) "initialization" +string(5) "Title" +string(7) "Content" +object(BlogPost)#%d (3) { + ["id"]=> + int(123) + ["title"]=> + string(5) "Title" + ["content"]=> + string(7) "Content" +} +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_005.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_005.phpt new file mode 100644 index 0000000000000..f8548f36b700d --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_005.phpt @@ -0,0 +1,71 @@ +--TEST-- +Lazy objects: RFC example 005 +--FILE-- + 'Title', + 'content' => 'Content', + ]; +} + +$reflector = new ReflectionClass(BlogPost::class); +// Callable that retrieves the title and content from the database. +$initializer = function ($blogPost) { + var_dump("initialization"); + $data = loadFromDB(); + return new BlogPost($blogPost->id, $data['title'], $data['content']); +}; +$post = $reflector->newLazyProxy($initializer); + +// Without this line, the following call to ReflectionProperty::setValue() would trigger initialization. +$reflector->getProperty('id')->skipLazyInitialization($post); +$reflector->getProperty('id')->setValue($post, 123); + +// Alternatively, one can use this directly: +$reflector->getProperty('id')->setRawValueWithoutLazyInitialization($post, 123); + +var_dump($post); +var_dump($post->id); +var_dump($post->title); +var_dump($post->content); +var_dump($post); + +?> +==DONE== +--EXPECTF-- +lazy proxy object(BlogPost)#%d (1) { + ["id"]=> + int(123) + ["title"]=> + uninitialized(string) + ["content"]=> + uninitialized(string) +} +int(123) +string(14) "initialization" +string(5) "Title" +string(7) "Content" +lazy proxy object(BlogPost)#%d (1) { + ["instance"]=> + object(BlogPost)#%d (3) { + ["id"]=> + int(123) + ["title"]=> + string(5) "Title" + ["content"]=> + string(7) "Content" + } +} +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_006.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_006.phpt new file mode 100644 index 0000000000000..c467567186495 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_006.phpt @@ -0,0 +1,64 @@ +--TEST-- +Lazy objects: RFC example 006 +--FILE-- +newLazyGhost(function ($object2) { + $object2->propB = 'value'; + throw new \Exception('initializer exception'); +}); +$reflector->getProperty('propA')->setRawValueWithoutLazyInitialization($object2, 'object-2'); + +$object1 = $reflector->newLazyGhost(function ($object1) use ($object2) { + $object1->propB = 'updated'; + $object1->propB = $object2->propB; +}); +$reflector->getProperty('propA')->setRawValueWithoutLazyInitialization($object1, 'object-1'); + +// Both objects are uninitalized at this point + +var_dump($object1); +var_dump($object2); + +try { + var_dump($object1->propB); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +// The state of both objects is unchanged + +var_dump($object1); +var_dump($object2); + +?> +==DONE== +--EXPECTF-- +lazy ghost object(MyClass)#%d (1) { + ["propA"]=> + string(8) "object-1" +} +lazy ghost object(MyClass)#%d (1) { + ["propA"]=> + string(8) "object-2" +} +initializer exception +lazy ghost object(MyClass)#%d (1) { + ["propA"]=> + string(8) "object-1" +} +lazy ghost object(MyClass)#%d (1) { + ["propA"]=> + string(8) "object-2" +} +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_007.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_007.phpt new file mode 100644 index 0000000000000..5310345428556 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_007.phpt @@ -0,0 +1,31 @@ +--TEST-- +Lazy objects: RFC example 007 +--FILE-- +resetAsLazyGhost($object, function () {}); +var_dump($id === spl_object_id($object)); +var_dump($ref->get() === $object); + +$reflector->initializeLazyObject($object); +var_dump($id === spl_object_id($object)); +var_dump($ref->get() === $object); + +?> +==DONE== +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_008.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_008.phpt new file mode 100644 index 0000000000000..7cbbcb21c5811 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_008.phpt @@ -0,0 +1,25 @@ +--TEST-- +Lazy objects: RFC example 008 +--FILE-- +newLazyGhost(function () { + throw new \Exception('initialization'); +}); + +$reflector->getProperty('id')->skipLazyInitialization($object); + +$object->id = 1; +var_dump($object->id); + +?> +==DONE== +--EXPECT-- +int(1) +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_009.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_009.phpt new file mode 100644 index 0000000000000..ccf7f53e6b260 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_009.phpt @@ -0,0 +1,40 @@ +--TEST-- +Lazy objects: RFC example 009 +--FILE-- +connect(); + } + public function __destruct() { + var_dump(__METHOD__); + $this->close(); + } + public function connect() { + } + public function close() { + } +} + +$connection = new Connection(); + +$reflector = new ReflectionClass(Connection::class); + +print "Reset non-lazy\n"; +// Calls destructor +$reflector->resetAsLazyGhost($connection, function () { + var_dump("initialization"); +}); + +print "Release non-initialized\n"; +$connection = null; // Does not call destructor (object is not initialized) + +?> +==DONE== +--EXPECT-- +Reset non-lazy +string(22) "Connection::__destruct" +Release non-initialized +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_010.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_010.phpt new file mode 100644 index 0000000000000..3c578a883ea90 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_010.phpt @@ -0,0 +1,25 @@ +--TEST-- +Lazy objects: RFC example 010 +--FILE-- +newLazyGhost(function () {}); + +$reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'value'); + +var_dump($obj); + +?> +==DONE== +--EXPECTF-- +lazy ghost object(MyClass)#%d (1) { + ["a"]=> + string(5) "value" +} +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_011.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_011.phpt new file mode 100644 index 0000000000000..2dc50b1a33ff3 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_011.phpt @@ -0,0 +1,81 @@ +--TEST-- +Lazy objects: RFC example 011 +--FILE-- +name; + } + + public function getEmail() + { + return $this->email; + } +} + +// ORM code + +class EntityManager +{ + public function getReference(string $class, int $id) + { + // The ReflectionClass and ReflectionProperty instances are cached in practice + $reflector = new ReflectionClass($class); + + $entity = $reflector->newLazyGhost(function ($entity) use ($class, $id, $reflector) { + $data = $this->loadFromDatabase($class, $id); + $reflector->getProperty('name')->setValue($entity, $data['name']); + $reflector->getProperty('email')->setValue($entity, $data['email']); + }); + + // id is already known and can be accessed without triggering initialization + $reflector->getProperty('id')->setRawValueWithoutLazyInitialization($entity, $id); + + return $entity; + } + + public function loadFromDatabase($id) + { + return [ + 'name' => 'Example', + 'email' => 'example@example.com', + ]; + } +} + +$em = new EntityManager(); +$blogPost = $em->getReference(BlogPost::class, 123); +var_dump($blogPost); +var_dump($blogPost->getName()); +var_dump($blogPost); + +?> +==DONE== +--EXPECTF-- +lazy ghost object(BlogPost)#%d (1) { + ["id":"BlogPost":private]=> + int(123) + ["name":"BlogPost":private]=> + uninitialized(string) + ["email":"BlogPost":private]=> + uninitialized(string) +} +string(7) "Example" +object(BlogPost)#%d (3) { + ["id":"BlogPost":private]=> + int(123) + ["name":"BlogPost":private]=> + string(7) "Example" + ["email":"BlogPost":private]=> + string(19) "example@example.com" +} +==DONE== diff --git a/Zend/tests/lazy_objects/rfc/rfc_example_012.phpt b/Zend/tests/lazy_objects/rfc/rfc_example_012.phpt new file mode 100644 index 0000000000000..14b07d3bd65e4 --- /dev/null +++ b/Zend/tests/lazy_objects/rfc/rfc_example_012.phpt @@ -0,0 +1,80 @@ +--TEST-- +Lazy objects: RFC example 012 +--FILE-- +hostname, $this->credentials); + } +} + +class Client +{ + public function __construct( + private string $hostname, + private string $credentials, + ) {} + + public function doSomething() + { + printf("doSomething() (hostname: %s)\n", $this->hostname); + } +} + +// Symfony code + +class Container +{ + public function getClientFactoryService(): ClientFactory + { + return new ClientFactory('127.0.0.1', 'secret'); + } + + public function getClientService(): Client + { + $reflector = new ReflectionClass(Client::class); + + $client = $reflector->newLazyProxy(function () { + $clientFactory = $this->getClientFactoryService(); + return $clientFactory->createClient(); + }); + + return $client; + } +} + +$container = new Container(); +$service = $container->getClientService(); +var_dump($service); +$service->doSomething(); +var_dump($service); + +?> +==DONE== +--EXPECTF-- +lazy proxy object(Client)#%d (0) { + ["hostname":"Client":private]=> + uninitialized(string) + ["credentials":"Client":private]=> + uninitialized(string) +} +doSomething() (hostname: 127.0.0.1) +lazy proxy object(Client)#%d (1) { + ["instance"]=> + object(Client)#%d (2) { + ["hostname":"Client":private]=> + string(9) "127.0.0.1" + ["credentials":"Client":private]=> + string(6) "secret" + } +} +==DONE== diff --git a/Zend/tests/lazy_objects/serialize___serialize_may_initialize.phpt b/Zend/tests/lazy_objects/serialize___serialize_may_initialize.phpt new file mode 100644 index 0000000000000..632f6e9780880 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize___serialize_may_initialize.phpt @@ -0,0 +1,53 @@ +--TEST-- +Lazy objects: serialize() initializes object if __serialize observes object state +--FILE-- + $this->a]; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $serialized = serialize($obj); + $unserialized = unserialize($serialized); + var_dump($serialized, $unserialized); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new c(); + $c->a = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/serialize___serialize_may_not_initialize.phpt b/Zend/tests/lazy_objects/serialize___serialize_may_not_initialize.phpt new file mode 100644 index 0000000000000..404a35ee10442 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize___serialize_may_not_initialize.phpt @@ -0,0 +1,48 @@ +--TEST-- +Lazy objects: serialize() does not initialize object if __serialize does observe object state +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(12) "O:1:"C":0:{}" +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +string(12) "O:1:"C":0:{}" +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/serialize___sleep_initializes.phpt b/Zend/tests/lazy_objects/serialize___sleep_initializes.phpt new file mode 100644 index 0000000000000..ec7657afc6916 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize___sleep_initializes.phpt @@ -0,0 +1,53 @@ +--TEST-- +Lazy objects: serialize initializes object by default if the __sleep method is used +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new C(); + $c->a = 1; + return $c; +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/serialize___sleep_skip_flag.phpt b/Zend/tests/lazy_objects/serialize___sleep_skip_flag.phpt new file mode 100644 index 0000000000000..4641fb2a49ac3 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize___sleep_skip_flag.phpt @@ -0,0 +1,49 @@ +--TEST-- +Lazy objects: serialize does not initializes object with __sleep method if flag is set +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(12) "O:1:"C":0:{}" +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +string(12) "O:1:"C":0:{}" +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/serialize___sleep_skip_flag_may_initialize.phpt b/Zend/tests/lazy_objects/serialize___sleep_skip_flag_may_initialize.phpt new file mode 100644 index 0000000000000..ed909a875de47 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize___sleep_skip_flag_may_initialize.phpt @@ -0,0 +1,56 @@ +--TEST-- +Lazy objects: __sleep initializes object if it observes object state, even with SKIP_INITIALIZATION_ON_SERIALIZE +--FILE-- +a); + return ['a']; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $serialized = serialize($obj); + $unserialized = unserialize($serialized); + var_dump($serialized, $unserialized); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->a = 1; +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $c = new C(); + $c->a = 1; + return $c; +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(11) "initializer" +int(1) +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +string(11) "initializer" +int(1) +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/serialize_dynamic_props.phpt b/Zend/tests/lazy_objects/serialize_dynamic_props.phpt new file mode 100644 index 0000000000000..b2465ded4f4b5 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_dynamic_props.phpt @@ -0,0 +1,48 @@ +--TEST-- +Lazy objects: serialize() with dynamic props on initialized object +--FILE-- +newLazyGhost(function ($obj) { + $obj->dyn = 1; +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + $c = new C(); + $c->dyn = 1; + return $c; +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["dyn"]=> + int(1) +} +# Proxy +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["dyn"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/serialize_failed_lazy_object.phpt b/Zend/tests/lazy_objects/serialize_failed_lazy_object.phpt new file mode 100644 index 0000000000000..7fdf8fb771652 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_failed_lazy_object.phpt @@ -0,0 +1,50 @@ +--TEST-- +Lazy objects: serialize() a lazy object that previously failed to initialize +--FILE-- +initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + try { + var_dump(unserialize(serialize($obj))); + } catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception('Initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('Initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECT-- +# Ghost +Exception: Initializer +Exception: Initializer +# Proxy +Exception: Initializer +Exception: Initializer diff --git a/Zend/tests/lazy_objects/serialize_failed_lazy_object_skip_init_on_serialize.phpt b/Zend/tests/lazy_objects/serialize_failed_lazy_object_skip_init_on_serialize.phpt new file mode 100644 index 0000000000000..4fbda267d52a4 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_failed_lazy_object_skip_init_on_serialize.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: serialize() a lazy object that previously failed to initialize, with SKIP_INITIALIZATION_ON_SERIALIZE +--FILE-- +initializeLazyObject($obj); + } catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + try { + var_dump(unserialize(serialize($obj))); + } catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception('Initializer'); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('Initializer'); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +Exception: Initializer +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + NULL +} +# Proxy +Exception: Initializer +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + NULL +} diff --git a/Zend/tests/lazy_objects/serialize_hook.phpt b/Zend/tests/lazy_objects/serialize_hook.phpt new file mode 100644 index 0000000000000..f2094db2e9a0a --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_hook.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: serialize during hook initializes object by default +--FILE-- +a = $value; } + } + public function __construct() { + var_dump(__METHOD__); + $this->a = 1; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj->a); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" diff --git a/Zend/tests/lazy_objects/serialize_initializes.phpt b/Zend/tests/lazy_objects/serialize_initializes.phpt new file mode 100644 index 0000000000000..eacb6a985df39 --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_initializes.phpt @@ -0,0 +1,44 @@ +--TEST-- +Lazy objects: serialize initializes object by default +--FILE-- +a = 1; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump(serialize($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" diff --git a/Zend/tests/lazy_objects/serialize_props_ht.phpt b/Zend/tests/lazy_objects/serialize_props_ht.phpt new file mode 100644 index 0000000000000..e8b8834b008de --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_props_ht.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: serialize initializes object by default (properties hashtable) +--FILE-- +a = 1; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + // Builds properties hashtable + get_object_vars($obj); + + var_dump(serialize($obj)); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECT-- +# Ghost: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" +# Proxy: +string(11) "initializer" +string(14) "C::__construct" +string(24) "O:1:"C":1:{s:1:"a";i:1;}" diff --git a/Zend/tests/lazy_objects/serialize_skip_flag.phpt b/Zend/tests/lazy_objects/serialize_skip_flag.phpt new file mode 100644 index 0000000000000..b7f75320fd51c --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_skip_flag.phpt @@ -0,0 +1,52 @@ +--TEST-- +Lazy objects: serialize does not initializes object if flag is set +--FILE-- +setRawValueWithoutLazyInitialization($obj, 1); + + $serialized = serialize($obj); + $unserialized = unserialize($serialized); + var_dump($serialized, $unserialized); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(24) "O:1:"C":1:{s:1:"b";i:1;}" +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + int(1) +} +# Proxy: +string(24) "O:1:"C":1:{s:1:"b";i:1;}" +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/serialize_skip_flag_props_ht.phpt b/Zend/tests/lazy_objects/serialize_skip_flag_props_ht.phpt new file mode 100644 index 0000000000000..b54f7846eb86d --- /dev/null +++ b/Zend/tests/lazy_objects/serialize_skip_flag_props_ht.phpt @@ -0,0 +1,55 @@ +--TEST-- +Lazy objects: serialize does not initializes object if flag is set (properties hashtable) +--FILE-- +setRawValueWithoutLazyInitialization($obj, 1); + + // Builds properties hashtable + get_mangled_object_vars($obj); + + $serialized = serialize($obj); + $unserialized = unserialize($serialized); + var_dump($serialized, $unserialized); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); +}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(24) "O:1:"C":1:{s:1:"b";i:1;}" +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + int(1) +} +# Proxy: +string(24) "O:1:"C":1:{s:1:"b";i:1;}" +object(C)#%d (1) { + ["a"]=> + uninitialized(int) + ["b"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization.phpt new file mode 100644 index 0000000000000..807476d70729a --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization.phpt @@ -0,0 +1,47 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() sets value +--FILE-- +getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + string(4) "test" +} +# Proxy +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + string(4) "test" +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_001.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_001.phpt new file mode 100644 index 0000000000000..d6eb417a15760 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_001.phpt @@ -0,0 +1,67 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() leaves property as lazy if exception prevents update +--FILE-- +getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // Prop is still lazy: This triggers initialization + $obj->a = 1; + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +TypeError: Cannot assign stdClass to property C::$a of type int +C::__construct +bool(true) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL +} +# Proxy +TypeError: Cannot assign stdClass to property C::$a of type int +C::__construct +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_002.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_002.phpt new file mode 100644 index 0000000000000..40221269893bd --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_exception_002.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() leaves non-lazy properties as non-lazy in case of exception +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + try { + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + // Prop is still non-lazy: This does not trigger initialization + $obj->a = 1; + var_dump($reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + throw new Exception('Unreachable'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new Exception('Unreachable'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +TypeError: Cannot assign stdClass to property C::$a of type int +bool(true) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(1) + ["b"]=> + uninitialized(int) +} +# Proxy +TypeError: Cannot assign stdClass to property C::$a of type int +bool(true) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(1) + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt new file mode 100644 index 0000000000000..caa5211f371a5 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_initialized.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() is allowed on initialized objects +--FILE-- +initializeLazyObject($obj); + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +object(C)#%d (2) { + ["a"]=> + string(4) "test" + ["b"]=> + NULL +} +# Proxy +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt new file mode 100644 index 0000000000000..9151e58f3fc65 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt @@ -0,0 +1,43 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() can not be called on dynamic properties +--FILE-- +dyn = 1; + $propReflector = new ReflectionProperty($c, 'dyn'); + + try { + $propReflector->setRawValueWithoutLazyInitialization($obj, 'test'); + } catch (\ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECT-- +# Ghost +ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn +# Proxy +ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly.phpt new file mode 100644 index 0000000000000..1e4a7ade935ad --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() preserves readonly semantics +--FILE-- +getProperty('a')->setRawValueWithoutLazyInitialization($obj, 1); + try { + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 2); + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +Error: Cannot modify readonly property C::$a +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy +Error: Cannot modify readonly property C::$a +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly_variant.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly_variant.phpt new file mode 100644 index 0000000000000..cdb1b66543d65 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_readonly_variant.phpt @@ -0,0 +1,76 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() preserves readonly semantics +--FILE-- +a = 1; + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + } + public readonly int $a; + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 2); + + var_dump($obj->a); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); + + $reflector->initializeLazyObject($obj); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +int(2) +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(2) +} +Error: Cannot modify readonly property C::$a +object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + NULL +} +# Proxy +int(2) +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(2) +} +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_realize.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_realize.phpt new file mode 100644 index 0000000000000..a3f0002c9cd6f --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_realize.phpt @@ -0,0 +1,52 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() may realize object if last lazy prop +--FILE-- +getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + $reflector->getProperty('b')->setRawValueWithoutLazyInitialization($obj, 'test'); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +bool(true) +object(C)#%d (2) { + ["a"]=> + string(4) "test" + ["b"]=> + string(4) "test" +} +# Proxy +bool(true) +object(C)#%d (2) { + ["a"]=> + string(4) "test" + ["b"]=> + string(4) "test" +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_destruct.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_destruct.phpt new file mode 100644 index 0000000000000..96bc8054ed49b --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_destruct.phpt @@ -0,0 +1,71 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() may trigger initialization via side effects (__destruct()) +--FILE-- +a = 'a'; + $this->b = 'b'; + } + public $a; + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + + $value = new class($obj) { + function __construct(public object $obj) {} + function __destruct() { + $this->obj->b = ''; + } + }; + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, $value); + $value = null; + + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, new stdClass); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +C::__construct +bool(true) +object(C)#%d (2) { + ["a"]=> + string(1) "a" + ["b"]=> + string(0) "" +} +# Proxy +C::__construct +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + string(1) "a" + ["b"]=> + string(0) "" + } +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_toString.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_toString.phpt new file mode 100644 index 0000000000000..1c8eac82880c0 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_side_effect_toString.phpt @@ -0,0 +1,68 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() may trigger initialization via side effects (__toString()) +--FILE-- +a = 'a'; + $this->b = 'b'; + } + public string $a; + public string $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + + $value = new class($obj) { + function __construct(public object $obj) {} + function __toString() { + return $this->obj->b; + } + }; + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, $value); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +C::__construct +bool(true) +object(C)#%d (2) { + ["a"]=> + string(1) "b" + ["b"]=> + string(1) "b" +} +# Proxy +C::__construct +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + string(1) "a" + ["b"]=> + string(1) "b" + } +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips___set.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips___set.phpt new file mode 100644 index 0000000000000..10a335f8fe5f0 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips___set.phpt @@ -0,0 +1,51 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() does not call __set() +--FILE-- +getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + string(4) "test" +} +# Proxy +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + string(4) "test" +} diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips_hook.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips_hook.phpt new file mode 100644 index 0000000000000..7bd6261519ec8 --- /dev/null +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_skips_hook.phpt @@ -0,0 +1,50 @@ +--TEST-- +Lazy objects: setRawValueWithoutLazyInitialization() skips hooks +--FILE-- +a = $value; } + get { return $this->a; } + } + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->setRawValueWithoutLazyInitialization($obj, 'test'); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + string(4) "test" +} +# Proxy +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + string(4) "test" +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization.phpt b/Zend/tests/lazy_objects/skipLazyInitialization.phpt new file mode 100644 index 0000000000000..faff902d0e418 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization.phpt @@ -0,0 +1,349 @@ +--TEST-- +Lazy objects: ReflectionProperty::skipLazyInitialization() prevent properties from triggering initializer +--FILE-- +hooked; } + set ($value) { $this->hooked = strtoupper($value); } + } + + public $virtual { + get { return 'virtual'; } + set ($value) { } + } +} + +class B extends A { + private $priv = 'privB'; + public $pubB = 'pubB'; + + private readonly string $readonly; +} + +set_error_handler(function ($errno, $errstr) { + throw new Error($errstr); +}); + +function testProperty(callable $makeObj, $propReflector) { + + $reflector = new ReflectionClass(B::class); + + $getValue = function ($obj, $propReflector) { + $name = $propReflector->getName(); + return $obj->$name; + }; + + printf("\n## %s", $propReflector); + + printf("\nskipInitializerForProperty():\n"); + $obj = $makeObj(); + $skept = false; + try { + $propReflector->skipLazyInitialization($obj); + $skept = true; + } catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + if (!$reflector->isUninitializedLazyObject($obj)) { + printf("Object was unexpectedly initialized (1)\n"); + } + if ($skept) { + try { + printf("getValue(): "); + var_dump($getValue($obj, $propReflector)); + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + if (!$propReflector->isStatic()) { + $propReflector->setValue($obj, ''); + } + if (!$reflector->isUninitializedLazyObject($obj)) { + printf("Object was unexpectedly initialized (1)\n"); + } + } + + printf("\nsetRawValueWithoutLazyInitialization():\n"); + $obj = $makeObj(); + $skept = false; + try { + $propReflector->setRawValueWithoutLazyInitialization($obj, 'value'); + $skept = true; + } catch (ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + if (!$reflector->isUninitializedLazyObject($obj)) { + printf("Object was unexpectedly initialized (1)\n"); + } + if ($skept) { + try { + printf("getValue(): "); + var_dump($getValue($obj, $propReflector)); + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + if (!$reflector->isUninitializedLazyObject($obj)) { + printf("Object was unexpectedly initialized (1)\n"); + } + } +} + +function test(string $name, callable $makeObj) { + printf("# %s:\n", $name); + + $reflector = new ReflectionClass(B::class); + + foreach ($reflector->getProperties() as $propReflector) { + testProperty($makeObj, $propReflector); + } + + testProperty($makeObj, new class { + function getName() { + return 'dynamicProp'; + } + function setValue($obj, $value) { + $obj->dynamicProp = $value; + } + function isStatic() { + return false; + } + // TODO: refactor this test + function skipLazyInitialization(object $object) { + throw new \ReflectionException(); + } + function setRawValueWithoutLazyInitialization(object $object) { + throw new \ReflectionException(); + } + function __toString() { + return "Property [ \$dynamicProp ]\n"; + } + }); +} + +$reflector = new ReflectionClass(B::class); + +$factory = function () use ($reflector) { + return $reflector->newLazyGhost(function ($obj) { + throw new \Exception('initializer'); + }); +}; + +test('Ghost', $factory); + +$factory = function () use ($reflector) { + return $reflector->newLazyGhost(function ($obj) { + throw new \Exception('initializer'); + }); +}; + +test('Proxy', $factory); + +?> +--EXPECT-- +# Ghost: + +## Property [ private $priv = 'privB' ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access private property B::$priv + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access private property B::$priv + +## Property [ public $pubB = 'pubB' ] + +skipInitializerForProperty(): +getValue(): string(4) "pubB" + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ private readonly string $readonly ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access private property B::$readonly + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access private property B::$readonly + +## Property [ protected $prot = 'prot' ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access protected property B::$prot + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access protected property B::$prot + +## Property [ public $pubA = 'pubA' ] + +skipInitializerForProperty(): +getValue(): string(4) "pubA" + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public static $static = 'static' ] + +skipInitializerForProperty(): +ReflectionException: Can not use skipLazyInitialization on static property B::$static + +setRawValueWithoutLazyInitialization(): +ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property B::$static + +## Property [ public $noDefault = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public string $noDefaultTyped ] + +skipInitializerForProperty(): +getValue(): Error: Typed property A::$noDefaultTyped must not be accessed before initialization + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $initialized = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $hooked = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $virtual ] + +skipInitializerForProperty(): +ReflectionException: Can not use skipLazyInitialization on virtual property B::$virtual + +setRawValueWithoutLazyInitialization(): +ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property B::$virtual + +## Property [ $dynamicProp ] + +skipInitializerForProperty(): +ReflectionException: + +setRawValueWithoutLazyInitialization(): +ReflectionException: +# Proxy: + +## Property [ private $priv = 'privB' ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access private property B::$priv + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access private property B::$priv + +## Property [ public $pubB = 'pubB' ] + +skipInitializerForProperty(): +getValue(): string(4) "pubB" + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ private readonly string $readonly ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access private property B::$readonly + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access private property B::$readonly + +## Property [ protected $prot = 'prot' ] + +skipInitializerForProperty(): +getValue(): Error: Cannot access protected property B::$prot + +setRawValueWithoutLazyInitialization(): +getValue(): Error: Cannot access protected property B::$prot + +## Property [ public $pubA = 'pubA' ] + +skipInitializerForProperty(): +getValue(): string(4) "pubA" + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public static $static = 'static' ] + +skipInitializerForProperty(): +ReflectionException: Can not use skipLazyInitialization on static property B::$static + +setRawValueWithoutLazyInitialization(): +ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property B::$static + +## Property [ public $noDefault = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public string $noDefaultTyped ] + +skipInitializerForProperty(): +getValue(): Error: Typed property A::$noDefaultTyped must not be accessed before initialization + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $initialized = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $hooked = NULL ] + +skipInitializerForProperty(): +getValue(): NULL + +setRawValueWithoutLazyInitialization(): +getValue(): string(5) "value" + +## Property [ public $virtual ] + +skipInitializerForProperty(): +ReflectionException: Can not use skipLazyInitialization on virtual property B::$virtual + +setRawValueWithoutLazyInitialization(): +ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property B::$virtual + +## Property [ $dynamicProp ] + +skipInitializerForProperty(): +ReflectionException: + +setRawValueWithoutLazyInitialization(): +ReflectionException: diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_default.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_default.phpt new file mode 100644 index 0000000000000..15859a79e74a4 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_default.phpt @@ -0,0 +1,59 @@ +--TEST-- +Lazy objects: skipLazyInitialization() marks property as non-lazy and sets default value if any +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + + var_dump($obj->a); + var_dump($obj->b); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +NULL +int(1) +bool(false) +lazy ghost object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) +} +# Proxy +NULL +int(1) +bool(false) +lazy proxy object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt new file mode 100644 index 0000000000000..bf8ff2094ca15 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_initialized_object.phpt @@ -0,0 +1,61 @@ +--TEST-- +Lazy objects: skipLazyInitialization() has no effect on initialized objects +--FILE-- +a = 2; + } + public $a = 1; + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->initializeLazyObject($obj); + $reflector->getProperty('a')->skipLazyInitialization($obj); + + var_dump($obj->a); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +int(2) +bool(true) +object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + NULL +} +# Proxy +int(1) +bool(true) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt new file mode 100644 index 0000000000000..74e12cb3629f8 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt @@ -0,0 +1,43 @@ +--TEST-- +Lazy objects: skipLazyInitialization() can not be used on dynamic properties +--FILE-- +dyn = 1; + $propReflector = new ReflectionProperty($c, 'dyn'); + + try { + $propReflector->skipLazyInitialization($obj); + } catch (\ReflectionException $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECT-- +# Ghost +ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn +# Proxy +ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_readonly.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_readonly.phpt new file mode 100644 index 0000000000000..dcb94f90b82ac --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_readonly.phpt @@ -0,0 +1,75 @@ +--TEST-- +Lazy objects: skipLazyInitialization() preserves readonly semantics +--FILE-- +a = 1; + } + public readonly int $a; + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->skipLazyInitialization($obj); + + try { + var_dump($obj->a); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); + + $reflector->initializeLazyObject($obj); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function ($obj) { + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +Error: Typed property C::$a must not be accessed before initialization +bool(false) +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL +} +# Proxy +Error: Typed property C::$a must not be accessed before initialization +bool(false) +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL + } +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_realize.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_realize.phpt new file mode 100644 index 0000000000000..084a3993876f4 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_realize.phpt @@ -0,0 +1,52 @@ +--TEST-- +Lazy objects: skipLazyInitialization() may realize object if last lazy prop +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +bool(true) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL +} +# Proxy +bool(true) +object(C)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + NULL +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_skips___set.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_skips___set.phpt new file mode 100644 index 0000000000000..761a2923e69a2 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_skips___set.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: skipLazyInitialization() does not call __set() +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + + var_dump($obj->a); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +int(1) +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy +int(1) +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_skips_hooks.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_skips_hooks.phpt new file mode 100644 index 0000000000000..779e931773b06 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_skips_hooks.phpt @@ -0,0 +1,53 @@ +--TEST-- +Lazy objects: skipLazyInitialization() skips hooks +--FILE-- +a = $value; } + get { return $this->a; } + } + public $b; +} + +function test(string $name, object $obj) { + printf("# %s\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->skipLazyInitialization($obj); + + var_dump($obj->a); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +int(1) +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy +int(1) +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(1) +} diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_skips_non_lazy_prop.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_skips_non_lazy_prop.phpt new file mode 100644 index 0000000000000..2244020fb6100 --- /dev/null +++ b/Zend/tests/lazy_objects/skipLazyInitialization_skips_non_lazy_prop.phpt @@ -0,0 +1,54 @@ +--TEST-- +Lazy objects: skipLazyInitialization() has no effect on non-lazy properties +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + + $obj->a = 2; + + $reflector->getProperty('a')->skipLazyInitialization($obj); + + var_dump($obj->a); + var_dump(!$reflector->isUninitializedLazyObject($obj)); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); +$obj = $reflector->newLazyGhost(function () { + throw new \Exception('initializer'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function () { + throw new \Exception('initializer'); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost +int(2) +bool(false) +lazy ghost object(C)#%d (1) { + ["a"]=> + int(2) +} +# Proxy +int(2) +bool(false) +lazy proxy object(C)#%d (1) { + ["a"]=> + int(2) +} diff --git a/Zend/tests/lazy_objects/support_no_internal_classes.phpt b/Zend/tests/lazy_objects/support_no_internal_classes.phpt new file mode 100644 index 0000000000000..f5c8cd1e06738 --- /dev/null +++ b/Zend/tests/lazy_objects/support_no_internal_classes.phpt @@ -0,0 +1,34 @@ +--TEST-- +Lazy objects: internal classes can not be initialized lazily +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +print "# Proxy:\n"; + +try { + $obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECTF-- +# Ghost: +Error: Cannot make instance of internal class lazy: DateTime is internal +# Proxy: +Error: Cannot make instance of internal class lazy: DateTime is internal diff --git a/Zend/tests/lazy_objects/support_no_internal_sub_classes.phpt b/Zend/tests/lazy_objects/support_no_internal_sub_classes.phpt new file mode 100644 index 0000000000000..47a8214626048 --- /dev/null +++ b/Zend/tests/lazy_objects/support_no_internal_sub_classes.phpt @@ -0,0 +1,37 @@ +--TEST-- +Lazy objects: sub-classes of internal classes can not be initialized lazily +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +print "# Proxy:\n"; + +try { + $obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->__construct(); + }); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +--EXPECTF-- +# Ghost: +Error: Cannot make instance of internal class lazy: C inherits internal class DateTime +# Proxy: +Error: Cannot make instance of internal class lazy: C inherits internal class DateTime diff --git a/Zend/tests/lazy_objects/support_readonly_class.phpt b/Zend/tests/lazy_objects/support_readonly_class.phpt new file mode 100644 index 0000000000000..cdd420cc121b8 --- /dev/null +++ b/Zend/tests/lazy_objects/support_readonly_class.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: readonly classes can be lazily initialized +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/support_readonly_prop.phpt b/Zend/tests/lazy_objects/support_readonly_prop.phpt new file mode 100644 index 0000000000000..2aabc3a6d18c4 --- /dev/null +++ b/Zend/tests/lazy_objects/support_readonly_prop.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: readonly properties can be lazily initialized +--FILE-- +a = 1; + } +} + +$reflector = new ReflectionClass(C::class); + +print "# Ghost:\n"; + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +var_dump($obj); +var_dump($obj->a); +var_dump($obj->a); +var_dump($obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +object(C)#%d (1) { + ["a"]=> + int(1) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +string(11) "initializer" +int(1) +int(1) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} diff --git a/Zend/tests/lazy_objects/support_stdClass.phpt b/Zend/tests/lazy_objects/support_stdClass.phpt new file mode 100644 index 0000000000000..dac4884584133 --- /dev/null +++ b/Zend/tests/lazy_objects/support_stdClass.phpt @@ -0,0 +1,30 @@ +--TEST-- +Lazy objects: stdClass can be initialized lazily +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); +var_dump($obj); + +--EXPECTF-- +# Ghost: +object(stdClass)#%d (0) { +} +# Proxy: +object(stdClass)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/support_stdClass_sub_classes.phpt b/Zend/tests/lazy_objects/support_stdClass_sub_classes.phpt new file mode 100644 index 0000000000000..bb73b2a54ec0f --- /dev/null +++ b/Zend/tests/lazy_objects/support_stdClass_sub_classes.phpt @@ -0,0 +1,33 @@ +--TEST-- +Lazy objects: sub-classes of stdClass can be initialized lazily +--FILE-- +newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); +var_dump($obj); + +print "# Proxy:\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); +var_dump($obj); + +--EXPECTF-- +# Ghost: +object(C)#%d (0) { +} +# Proxy: +object(C)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/unclean_shutdown.phpt b/Zend/tests/lazy_objects/unclean_shutdown.phpt new file mode 100644 index 0000000000000..9ec02b5a7c4d7 --- /dev/null +++ b/Zend/tests/lazy_objects/unclean_shutdown.phpt @@ -0,0 +1,19 @@ +--TEST-- +Lazy objects: unclean shutdown +--FILE-- +newLazyGhost(function ($obj) { + // Trigger a fatal error to get an unclean shutdown + class bool {} +}); + +var_dump($obj->a); +--EXPECTF-- +Fatal error: Cannot use 'bool' as class name%s on line %d diff --git a/Zend/tests/lazy_objects/unset_defined_no_initialize.phpt b/Zend/tests/lazy_objects/unset_defined_no_initialize.phpt new file mode 100644 index 0000000000000..7f09504564d2b --- /dev/null +++ b/Zend/tests/lazy_objects/unset_defined_no_initialize.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: unset of defined property does not initialize object +--FILE-- +a = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + (new ReflectionProperty($obj, 'a'))->setRawValueWithoutLazyInitialization($obj, 1); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +object(C)#%d (1) { + ["a"]=> + int(1) +} +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +# Proxy: +object(C)#%d (1) { + ["a"]=> + int(1) +} +object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/unset_hook.phpt b/Zend/tests/lazy_objects/unset_hook.phpt new file mode 100644 index 0000000000000..e174b22f2cdc4 --- /dev/null +++ b/Zend/tests/lazy_objects/unset_hook.phpt @@ -0,0 +1,68 @@ +--TEST-- +Lazy objects: cannot unset hooked property +--FILE-- +a; } + set($value) { $this->a = $value; } + } + public int $b = 1; + + public function __construct(int $a) { + var_dump(__METHOD__); + $this->a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + try { + unset($obj->a); + } catch (\Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +Error: Cannot unset hooked property C::$a +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +Error: Cannot unset hooked property C::$a +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/unset_magic_circular_may_initialize.phpt b/Zend/tests/lazy_objects/unset_magic_circular_may_initialize.phpt new file mode 100644 index 0000000000000..0705db85fad9a --- /dev/null +++ b/Zend/tests/lazy_objects/unset_magic_circular_may_initialize.phpt @@ -0,0 +1,68 @@ +--TEST-- +Lazy objects: circular unset of magic property may initialize object +--FILE-- +b = 2; + } + + public function __unset($name) { + unset($this->a); + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/unset_magic_may_initialize.phpt b/Zend/tests/lazy_objects/unset_magic_may_initialize.phpt new file mode 100644 index 0000000000000..4b0cf95763109 --- /dev/null +++ b/Zend/tests/lazy_objects/unset_magic_may_initialize.phpt @@ -0,0 +1,70 @@ +--TEST-- +Lazy objects: unset of magic property initializes object if method observes object state +--FILE-- +b = 2; + } + + public function __unset($name) { + var_dump($this->b); + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(2) +object(C)#%d (1) { + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +int(2) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/unset_magic_may_not_initialize.phpt b/Zend/tests/lazy_objects/unset_magic_may_not_initialize.phpt new file mode 100644 index 0000000000000..8d22cb4a3cdc9 --- /dev/null +++ b/Zend/tests/lazy_objects/unset_magic_may_not_initialize.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: unset of magic property may not initialize object if method does not observe object state +--FILE-- +b = 2; + } + + public function __unset($name) { + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/unset_skipped_no_initialize.phpt b/Zend/tests/lazy_objects/unset_skipped_no_initialize.phpt new file mode 100644 index 0000000000000..d990b696c24fc --- /dev/null +++ b/Zend/tests/lazy_objects/unset_skipped_no_initialize.phpt @@ -0,0 +1,78 @@ +--TEST-- +Lazy objects: unset of undefined skipped property initializes object +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + $reflector = new ReflectionClass(C::class); + $reflector->getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + $reflector->getProperty('c')->skipLazyInitialization($obj); + + var_dump($obj); + unset($obj->a); + unset($obj->b); + unset($obj->c); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); +--EXPECTF-- +# Ghost: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +object(C)#%d (0) { + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) +} +# Proxy: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +object(C)#%d (0) { + ["b"]=> + uninitialized(int) + ["c"]=> + uninitialized(int) +} diff --git a/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes.phpt b/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes.phpt new file mode 100644 index 0000000000000..792ca9c1cf7b7 --- /dev/null +++ b/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes.phpt @@ -0,0 +1,65 @@ +--TEST-- +Lazy objects: unset of undefined dynamic property initializes object +--FILE-- +b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes_no_props_ht.phpt b/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes_no_props_ht.phpt new file mode 100644 index 0000000000000..c27198f3f1968 --- /dev/null +++ b/Zend/tests/lazy_objects/unset_undefined_dynamic_initializes_no_props_ht.phpt @@ -0,0 +1,60 @@ +--TEST-- +Lazy objects: unset of undefined dynamic property initializes object (2) +--FILE-- +b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + // no var_dump($obj), so that properties ht is not initialized + var_dump('before unset'); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +string(12) "before unset" +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["b"]=> + int(2) +} +# Proxy: +string(12) "before unset" +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/unset_undefined_initializes.phpt b/Zend/tests/lazy_objects/unset_undefined_initializes.phpt new file mode 100644 index 0000000000000..59fc95f902e8f --- /dev/null +++ b/Zend/tests/lazy_objects/unset_undefined_initializes.phpt @@ -0,0 +1,66 @@ +--TEST-- +Lazy objects: unset of undefined property initializes object +--FILE-- +a = $a; + $this->b = 2; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + unset($obj->a); + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(1); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(1); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["b"]=> + int(2) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["b"]=> + int(2) + } +} diff --git a/Zend/tests/lazy_objects/use_case_001.phpt b/Zend/tests/lazy_objects/use_case_001.phpt new file mode 100644 index 0000000000000..c2be34bfebc28 --- /dev/null +++ b/Zend/tests/lazy_objects/use_case_001.phpt @@ -0,0 +1,44 @@ +--TEST-- +Lazy objects: Lazy service initialization in dependency injection container +--FILE-- +newLazyGhost(function ($obj) { + $obj->__construct(); + }); + return $obj; + } + + public function getApplicationService(): Application { + return new Application($this->getEntityManagerService()); + } +} + +$container = new Container(); + +printf("Service can be fetched without initializing dependencies\n"); +$application = $container->getApplicationService(); +--EXPECTF-- +Service can be fetched without initializing dependencies +string(24) "Application::__construct" diff --git a/Zend/tests/lazy_objects/use_case_001b.phpt b/Zend/tests/lazy_objects/use_case_001b.phpt new file mode 100644 index 0000000000000..f2491942d6e87 --- /dev/null +++ b/Zend/tests/lazy_objects/use_case_001b.phpt @@ -0,0 +1,44 @@ +--TEST-- +Lazy objects: Lazy service initialization in dependency injection container via factory +--FILE-- +newLazyProxy(function ($obj) { + return new EntityManager(); + }); + return $obj; + } + + public function getApplicationService(): Application { + return new Application($this->getEntityManagerService()); + } +} + +$container = new Container(); + +printf("Service can be fetched without initializing dependencies\n"); +$application = $container->getApplicationService(); +--EXPECTF-- +Service can be fetched without initializing dependencies +string(24) "Application::__construct" diff --git a/Zend/tests/lazy_objects/use_case_002.phpt b/Zend/tests/lazy_objects/use_case_002.phpt new file mode 100644 index 0000000000000..3a87acdfb8db3 --- /dev/null +++ b/Zend/tests/lazy_objects/use_case_002.phpt @@ -0,0 +1,52 @@ +--TEST-- +Lazy objects: Lazy entity loading +--FILE-- +id; + } + + public function getName(): string { + return $this->name; + } +} + +class EntityManager { + public function lazyLoad(string $fqcn, int $id): object { + $reflector = new ReflectionClass($fqcn); + $entity = $reflector->newLazyGhost(function ($obj) { + var_dump('initializer'); + $prop = new ReflectionProperty($obj::class, 'name'); + $prop->setValue($obj, 'John Doe'); + }); + + (new ReflectionProperty($fqcn, 'id'))->setRawValueWithoutLazyInitialization($entity, $id); + + return $entity; + } +} + +$em = new EntityManager(); +$user = $em->lazyLoad(User::class, 123); + +printf("Fetching identifier does not trigger initializer\n"); +var_dump($user->getId()); + +printf("Fetching anything else triggers initializer\n"); +var_dump($user->getName()); +--EXPECTF-- +Fetching identifier does not trigger initializer +int(123) +Fetching anything else triggers initializer +string(11) "initializer" +string(8) "John Doe" diff --git a/Zend/tests/lazy_objects/write_dynamic_initializes.phpt b/Zend/tests/lazy_objects/write_dynamic_initializes.phpt new file mode 100644 index 0000000000000..7864426463456 --- /dev/null +++ b/Zend/tests/lazy_objects/write_dynamic_initializes.phpt @@ -0,0 +1,74 @@ +--TEST-- +Lazy objects: property write to dynamic property initializes object +--FILE-- +a = 1; + $this->b = 2; + } +} +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->custom = 3; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (3) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["custom"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (3) { + ["a"]=> + int(1) + ["b"]=> + int(2) + ["custom"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/write_initializer_exception.phpt b/Zend/tests/lazy_objects/write_initializer_exception.phpt new file mode 100644 index 0000000000000..2445da55d21d6 --- /dev/null +++ b/Zend/tests/lazy_objects/write_initializer_exception.phpt @@ -0,0 +1,48 @@ +--TEST-- +Lazy objects: property write with initializer exception +--FILE-- +a = 1; + } catch (Exception $e) { + printf("%s\n", $e->getMessage()); + } + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + throw new \Exception('init exception'); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + throw new \Exception('init exception'); +}); + +test('Ghost', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { +} +init exception +lazy ghost object(C)#%d (0) { +} +# Ghost: +lazy proxy object(C)#%d (0) { +} +init exception +lazy proxy object(C)#%d (0) { +} diff --git a/Zend/tests/lazy_objects/write_initializes.phpt b/Zend/tests/lazy_objects/write_initializes.phpt new file mode 100644 index 0000000000000..2505352308719 --- /dev/null +++ b/Zend/tests/lazy_objects/write_initializes.phpt @@ -0,0 +1,69 @@ +--TEST-- +Lazy objects: property write initializes object +--FILE-- +b = 3; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a = 2; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { + ["b"]=> + uninitialized(int) +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (2) { + ["a"]=> + int(2) + ["b"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/write_magic_circular_may_initialize.phpt b/Zend/tests/lazy_objects/write_magic_circular_may_initialize.phpt new file mode 100644 index 0000000000000..52a7ae90cbde0 --- /dev/null +++ b/Zend/tests/lazy_objects/write_magic_circular_may_initialize.phpt @@ -0,0 +1,64 @@ +--TEST-- +Lazy objects: circular property write to magic property initializes object +--FILE-- +a = 1; + } + + public function __set($name, $value) { + $this->a = $value; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->a = 3; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["a"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/write_magic_may_initialize.phpt b/Zend/tests/lazy_objects/write_magic_may_initialize.phpt new file mode 100644 index 0000000000000..b90bb3cbc57b1 --- /dev/null +++ b/Zend/tests/lazy_objects/write_magic_may_initialize.phpt @@ -0,0 +1,64 @@ +--TEST-- +Lazy objects: property write to magic property initializes object if method updates object state +--FILE-- +a = 1; + } + + public function __set($name, $value) { + $this->a = $value; + } +} + +function test(string $name, object $obj) { + printf("# %s:\n", $name); + + var_dump($obj); + $obj->magic = 3; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +lazy ghost object(C)#%d (0) { +} +string(11) "initializer" +string(14) "C::__construct" +object(C)#%d (1) { + ["a"]=> + int(3) +} +# Proxy: +lazy proxy object(C)#%d (0) { +} +string(11) "initializer" +string(14) "C::__construct" +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(3) + } +} diff --git a/Zend/tests/lazy_objects/write_skipped_no_initialize.phpt b/Zend/tests/lazy_objects/write_skipped_no_initialize.phpt new file mode 100644 index 0000000000000..387b1a1cd8fed --- /dev/null +++ b/Zend/tests/lazy_objects/write_skipped_no_initialize.phpt @@ -0,0 +1,81 @@ +--TEST-- +Lazy objects: write to skipped property does not initialize object +--FILE-- +getProperty('a')->skipLazyInitialization($obj); + $reflector->getProperty('b')->skipLazyInitialization($obj); + $reflector->getProperty('c')->skipLazyInitialization($obj); + + var_dump($obj); + $obj->a = 2; + $obj->b = 2; + $obj->c = 2; + var_dump($obj); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +--EXPECTF-- +# Ghost: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +object(C)#%d (3) { + ["a"]=> + int(2) + ["b"]=> + int(2) + ["c"]=> + int(2) +} +# Proxy: +object(C)#%d (2) { + ["a"]=> + NULL + ["b"]=> + int(1) + ["c"]=> + uninitialized(int) +} +object(C)#%d (3) { + ["a"]=> + int(2) + ["b"]=> + int(2) + ["c"]=> + int(2) +} diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 1d10f61e62175..b4f13bedecc56 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1795,7 +1795,7 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties) * calling zend_merge_properties(). */ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties) /* {{{ */ { - if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) { + if (UNEXPECTED(class_type->ce_flags & ZEND_ACC_UNINSTANTIABLE)) { if (class_type->ce_flags & ZEND_ACC_INTERFACE) { zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name)); } else if (class_type->ce_flags & ZEND_ACC_TRAIT) { @@ -1803,6 +1803,7 @@ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zen } else if (class_type->ce_flags & ZEND_ACC_ENUM) { zend_throw_error(NULL, "Cannot instantiate enum %s", ZSTR_VAL(class_type->name)); } else { + ZEND_ASSERT(class_type->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)); zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name)); } ZVAL_NULL(arg); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 377bc23c46bd2..654ba7e7ea129 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -885,7 +885,7 @@ ZEND_FUNCTION(get_mangled_object_vars) Z_PARAM_OBJ(obj) ZEND_PARSE_PARAMETERS_END(); - properties = obj->handlers->get_properties(obj); + properties = zend_get_properties_no_lazy_init(obj); if (!properties) { ZVAL_EMPTY_ARRAY(return_value); return; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 92a31764a89f8..0f7c39676c614 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -398,7 +398,6 @@ typedef struct _zend_oparray_context { /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ - #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) @@ -418,6 +417,14 @@ static zend_always_inline uint32_t zend_visibility_to_set_visibility(uint32_t vi /* call through internal function handler. e.g. Closure::invoke() */ #define ZEND_ACC_CALL_VIA_HANDLER ZEND_ACC_CALL_VIA_TRAMPOLINE +#define ZEND_ACC_UNINSTANTIABLE ( \ + ZEND_ACC_INTERFACE | \ + ZEND_ACC_TRAIT | \ + ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | \ + ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | \ + ZEND_ACC_ENUM \ +) + #define ZEND_SHORT_CIRCUITING_CHAIN_MASK 0x3 #define ZEND_SHORT_CIRCUITING_CHAIN_EXPR 0 #define ZEND_SHORT_CIRCUITING_CHAIN_ISSET 1 diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index f1f5bfc84516f..21e17fe8c666b 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -171,6 +171,7 @@ void init_executor(void) /* {{{ */ zend_stack_init(&EG(user_exception_handlers), sizeof(zval)); zend_objects_store_init(&EG(objects_store), 1024); + zend_lazy_objects_init(&EG(lazy_objects_store)); EG(full_tables_cleanup) = 0; ZEND_ATOMIC_BOOL_INIT(&EG(vm_interrupt), false); @@ -492,6 +493,7 @@ void shutdown_executor(void) /* {{{ */ zend_stack_destroy(&EG(user_error_handlers_error_reporting)); zend_stack_destroy(&EG(user_error_handlers)); zend_stack_destroy(&EG(user_exception_handlers)); + zend_lazy_objects_destroy(&EG(lazy_objects_store)); zend_objects_store_destroy(&EG(objects_store)); if (EG(in_autoload)) { zend_hash_destroy(EG(in_autoload)); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 3aa967b8dd6bb..539793c8325a6 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -42,6 +42,7 @@ #include "zend_call_stack.h" #include "zend_max_execution_timer.h" #include "zend_strtod.h" +#include "zend_lazy_objects.h" /* Define ZTS if you want a thread-safe Zend */ /*#undef ZTS*/ @@ -246,6 +247,7 @@ struct _zend_executor_globals { zend_ini_entry *error_reporting_ini_entry; zend_objects_store objects_store; + zend_lazy_objects_store lazy_objects_store; zend_object *exception, *prev_exception; const zend_op *opline_before_exception; zend_op exception_op[3]; diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c new file mode 100644 index 0000000000000..357150bc63876 --- /dev/null +++ b/Zend/zend_lazy_objects.c @@ -0,0 +1,736 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | + +----------------------------------------------------------------------+ +*/ + +/* Lazy objects are standard zend_object whose initialization is defered until + * one of their properties backing store is accessed for the first time. + * + * This is implemented by using the same fallback mechanism as __get and __set + * magic methods that is triggered when an undefined property is accessed. + * + * Execution of methods or virtual property hooks do not trigger initialization + * until they access properties. + * + * A lazy object can be created via the Reflection API. The user specifies an + * initializer function that is called when initialization is required. + * + * There are two kinds of lazy objects: + * + * - Ghosts: These are initialized in-place by the initializer function + * - Proxy: The initializer returns a new instance. After initialization, + * interaction with the proxy object are proxied to the instance. + * + * Internal objects are not supported. + */ + +#include "zend_API.h" +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_gc.h" +#include "zend_hash.h" +#include "zend_object_handlers.h" +#include "zend_objects_API.h" +#include "zend_operators.h" +#include "zend_types.h" +#include "zend_variables.h" +#include "zend_lazy_objects.h" + +/** + * Information about each lazy object is stored outside of zend_objects, in + * EG(lazy_objects_store). For ghost objects, we can release this after the + * object is initialized. + */ +typedef struct _zend_lazy_object_info { + union { + struct { + zend_fcall_info_cache fcc; + zval zv; /* ReflectionClass::getLazyInitializer() */ + } initializer; + zend_object *instance; /* For initialized lazy proxy objects */ + } u; + zend_lazy_object_flags_t flags; + int lazy_properties_count; +} zend_lazy_object_info; + +/* zend_hash dtor_func_t for zend_lazy_objects_store.infos */ +static void zend_lazy_object_info_dtor_func(zval *pElement) +{ + zend_lazy_object_info *info = (zend_lazy_object_info*) Z_PTR_P(pElement); + + if (info->flags & ZEND_LAZY_OBJECT_INITIALIZED) { + ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_STRATEGY_PROXY); + zend_object_release(info->u.instance); + } else { + zval_ptr_dtor(&info->u.initializer.zv); + zend_fcc_dtor(&info->u.initializer.fcc); + } + + efree(info); +} + +void zend_lazy_objects_init(zend_lazy_objects_store *store) +{ + zend_hash_init(&store->infos, 8, NULL, zend_lazy_object_info_dtor_func, false); +} + +void zend_lazy_objects_destroy(zend_lazy_objects_store *store) +{ + ZEND_ASSERT(zend_hash_num_elements(&store->infos) == 0 || CG(unclean_shutdown)); + zend_hash_destroy(&store->infos); +} + +static void zend_lazy_object_set_info(zend_object *obj, zend_lazy_object_info *info) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + + zval *zv = zend_hash_index_add_new_ptr(&EG(lazy_objects_store).infos, obj->handle, info); + ZEND_ASSERT(zv); + (void)zv; +} + +static zend_lazy_object_info* zend_lazy_object_get_info(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + + zend_lazy_object_info *info = zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle); + ZEND_ASSERT(info); + + return info; +} + +static bool zend_lazy_object_has_stale_info(zend_object *obj) +{ + return zend_hash_index_find_ptr(&EG(lazy_objects_store).infos, obj->handle); +} + +zval* zend_lazy_object_get_initializer_zv(zend_object *obj) +{ + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + + ZEND_ASSERT(!(info->flags & ZEND_LAZY_OBJECT_INITIALIZED)); + + return &info->u.initializer.zv; +} + +static zend_fcall_info_cache* zend_lazy_object_get_initializer_fcc(zend_object *obj) +{ + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + + ZEND_ASSERT(!(info->flags & ZEND_LAZY_OBJECT_INITIALIZED)); + + return &info->u.initializer.fcc; +} + +zend_object* zend_lazy_object_get_instance(zend_object *obj) +{ + ZEND_ASSERT(zend_lazy_object_initialized(obj)); + + if (zend_object_is_lazy_proxy(obj)) { + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + + ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED); + + return info->u.instance; + } + + return obj; +} + +zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj) +{ + return zend_lazy_object_get_info(obj)->flags; +} + +void zend_lazy_object_del_info(zend_object *obj) +{ + zend_result res = zend_hash_index_del(&EG(lazy_objects_store).infos, obj->handle); + ZEND_ASSERT(res == SUCCESS); +} + +bool zend_lazy_object_decr_lazy_props(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + + ZEND_ASSERT(info->lazy_properties_count > 0); + + info->lazy_properties_count--; + + return info->lazy_properties_count == 0; +} + +/** + * Making objects lazy + */ + +ZEND_API bool zend_class_can_be_lazy(zend_class_entry *ce) +{ + /* Internal classes are not supported */ + if (UNEXPECTED(ce->type == ZEND_INTERNAL_CLASS && ce != zend_standard_class_def)) { + return false; + } + + for (zend_class_entry *parent = ce->parent; parent; parent = parent->parent) { + if (UNEXPECTED(parent->type == ZEND_INTERNAL_CLASS && parent != zend_standard_class_def)) { + return false; + } + } + + return true; +} + +/* Make object 'obj' lazy. If 'obj' is NULL, create a lazy instance of + * class 'reflection_ce' */ +ZEND_API zend_object *zend_object_make_lazy(zend_object *obj, + zend_class_entry *reflection_ce, zval *initializer_zv, + zend_fcall_info_cache *initializer_fcc, zend_lazy_object_flags_t flags) +{ + ZEND_ASSERT(!(flags & ~(ZEND_LAZY_OBJECT_USER_MASK|ZEND_LAZY_OBJECT_STRATEGY_MASK))); + ZEND_ASSERT((flags & ZEND_LAZY_OBJECT_STRATEGY_MASK) == ZEND_LAZY_OBJECT_STRATEGY_GHOST + || (flags & ZEND_LAZY_OBJECT_STRATEGY_MASK) == ZEND_LAZY_OBJECT_STRATEGY_PROXY); + + ZEND_ASSERT(!obj || (!zend_object_is_lazy(obj) || zend_lazy_object_initialized(obj))); + ZEND_ASSERT(!obj || instanceof_function(obj->ce, reflection_ce)); + + /* Internal classes are not supported */ + if (UNEXPECTED(reflection_ce->type == ZEND_INTERNAL_CLASS && reflection_ce != zend_standard_class_def)) { + zend_throw_error(NULL, "Cannot make instance of internal class lazy: %s is internal", ZSTR_VAL(reflection_ce->name)); + return NULL; + } + + for (zend_class_entry *parent = reflection_ce->parent; parent; parent = parent->parent) { + if (UNEXPECTED(parent->type == ZEND_INTERNAL_CLASS && parent != zend_standard_class_def)) { + zend_throw_error(NULL, "Cannot make instance of internal class lazy: %s inherits internal class %s", + ZSTR_VAL(reflection_ce->name), ZSTR_VAL(parent->name)); + return NULL; + } + } + + int lazy_properties_count = 0; + + if (!obj) { + if (UNEXPECTED(reflection_ce->ce_flags & ZEND_ACC_UNINSTANTIABLE)) { + zval zobj; + /* Call object_init_ex() for the generated exception */ + zend_result result = object_init_ex(&zobj, reflection_ce); + ZEND_ASSERT(result == FAILURE && EG(exception)); + (void)result; + return NULL; + } + + obj = zend_objects_new(reflection_ce); + + for (int i = 0; i < obj->ce->default_properties_count; i++) { + zval *p = &obj->properties_table[i]; + ZVAL_UNDEF(p); + if (EXPECTED(obj->ce->properties_info_table[i])) { + Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; + lazy_properties_count++; + } else { + Z_PROP_FLAG_P(p) = 0; + } + } + } else { + if (zend_object_is_lazy(obj)) { + ZEND_ASSERT(zend_object_is_lazy_proxy(obj) && zend_lazy_object_initialized(obj)); + OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); + zend_lazy_object_del_info(obj); + } else { + if (zend_lazy_object_has_stale_info(obj)) { + zend_throw_error(NULL, "Can not reset an object while it is being initialized"); + return NULL; + } + + if (!(flags & ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR) + && !(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { + if (obj->handlers->dtor_obj != zend_objects_destroy_object + || obj->ce->destructor) { + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + GC_ADDREF(obj); + obj->handlers->dtor_obj(obj); + GC_DELREF(obj); + if (EG(exception)) { + return NULL; + } + } + } + } + + GC_DEL_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + + /* unset() dynamic properties */ + zend_object_dtor_dynamic_properties(obj); + obj->properties = NULL; + + /* unset() declared properties */ + for (int i = 0; i < reflection_ce->default_properties_count; i++) { + zend_property_info *prop_info = obj->ce->properties_info_table[i]; + if (EXPECTED(prop_info)) { + zval *p = &obj->properties_table[i]; + if (Z_TYPE_P(p) != IS_UNDEF) { + if ((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(p) & IS_PROP_REINITABLE) + /* TODO: test final property */ + && ((obj->ce->ce_flags & ZEND_ACC_FINAL) || (prop_info->flags & ZEND_ACC_FINAL))) { + continue; + } + zend_object_dtor_property(obj, p); + ZVAL_UNDEF(p); + } + Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; + lazy_properties_count++; + } + } + } + + /* Objects become non-lazy if all properties are made non-lazy before + * initialization is triggerd. If the object has no properties to begin + * with, this happens immediately. */ + if (UNEXPECTED(!lazy_properties_count)) { + return obj; + } + + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED; + + if (flags & ZEND_LAZY_OBJECT_STRATEGY_PROXY) { + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY; + } else { + ZEND_ASSERT(flags & ZEND_LAZY_OBJECT_STRATEGY_GHOST); + } + + zend_lazy_object_info *info = emalloc(sizeof(*info)); + zend_fcc_dup(&info->u.initializer.fcc, initializer_fcc); + ZVAL_COPY(&info->u.initializer.zv, initializer_zv); + info->flags = flags; + info->lazy_properties_count = lazy_properties_count; + zend_lazy_object_set_info(obj, info); + + return obj; +} + +/** + * Initialization of lazy objects + */ + +/* Mark object as initialized. Lazy properties are initialized to their default + * value and the initializer is not called. */ +ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_class_entry *ce = obj->ce; + + if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + } + + zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce); + zval *properties_table = obj->properties_table; + + OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); + + for (int i = 0; i < ce->default_properties_count; i++) { + if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) { + ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]); + } + } + + zend_lazy_object_del_info(obj); + + return obj; +} + +/* Revert initializer effects */ +static void zend_lazy_object_revert_init(zend_object *obj, zval *properties_table_snapshot, HashTable *properties_snapshot) +{ + zend_class_entry *ce = obj->ce; + + if (ce->default_properties_count) { + ZEND_ASSERT(properties_table_snapshot); + zval *properties_table = obj->properties_table; + + for (int i = 0; i < ce->default_properties_count; i++) { + zval *p = &properties_table[i]; + zend_object_dtor_property(obj, p); + ZVAL_COPY_VALUE_PROP(p, &properties_table_snapshot[i]); + + zend_property_info *prop_info = ce->properties_info_table[i]; + if (Z_ISREF_P(p) && prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(p), prop_info); + } + } + + efree(properties_table_snapshot); + } + if (properties_snapshot) { + if (obj->properties != properties_snapshot) { + ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) >= 1); + zend_release_properties(obj->properties); + obj->properties = properties_snapshot; + } else { + ZEND_ASSERT((GC_FLAGS(properties_snapshot) & IS_ARRAY_IMMUTABLE) || GC_REFCOUNT(properties_snapshot) > 1); + zend_release_properties(properties_snapshot); + } + } else if (obj->properties) { + zend_release_properties(obj->properties); + obj->properties = NULL; + } + + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED; +} + +static bool zend_lazy_object_compatible(zend_object *real_object, zend_object *lazy_object) +{ + if (EXPECTED(real_object->ce == lazy_object->ce)) { + return true; + } + + if (!instanceof_function(lazy_object->ce, real_object->ce)) { + return false; + } + + /* zend_hash_num_elements(ce.properties_info) reports the actual number of + * properties. ce.default_properties_count is off by the number of property + * overrides. */ + if (zend_hash_num_elements(&lazy_object->ce->properties_info) != zend_hash_num_elements(&real_object->ce->properties_info)) { + return false; + } + + return lazy_object->ce->destructor == real_object->ce->destructor + && lazy_object->ce->clone == real_object->ce->clone; +} + +/* Initialize a lazy proxy object */ +static zend_object *zend_lazy_object_init_proxy(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy_proxy(obj)); + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + + /* prevent reentrant initialization */ + OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY); + + /* Call factory */ + zval retval; + int argc = 1; + zval zobj; + HashTable *named_params = NULL; + zend_fcall_info_cache *initializer = &info->u.initializer.fcc; + + ZVAL_OBJ(&zobj, obj); + + zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params); + + if (UNEXPECTED(EG(exception))) { + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; + return NULL; + } + + if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT)) { + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; + zend_type_error("Lazy proxy factory must return an instance of a class compatible with %s, %s returned", + ZSTR_VAL(obj->ce->name), + zend_zval_value_name(&retval)); + zval_ptr_dtor(&retval); + return NULL; + + } + + if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || !zend_lazy_object_compatible(Z_OBJ(retval), obj))) { + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; + zend_type_error("The real instance class %s is not compatible with the proxy class %s. The proxy must be a instance of the same class as the real instance, or a sub-class with no additional properties, and no overrides of the __destructor or __clone methods.", + zend_zval_value_name(&retval), + ZSTR_VAL(obj->ce->name)); + zval_ptr_dtor(&retval); + return NULL; + } + + if (UNEXPECTED(Z_OBJ(retval) == obj || zend_object_is_lazy(Z_OBJ(retval)))) { + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_UNINITIALIZED|IS_OBJ_LAZY_PROXY; + zend_throw_error(NULL, "Lazy proxy factory must return a non-lazy object"); + zval_ptr_dtor(&retval); + return NULL; + } + + zend_fcc_dtor(&info->u.initializer.fcc); + zval_ptr_dtor(&info->u.initializer.zv); + info->u.instance = Z_OBJ(retval); + info->flags |= ZEND_LAZY_OBJECT_INITIALIZED; + OBJ_EXTRA_FLAGS(obj) |= IS_OBJ_LAZY_PROXY; + + /* unset() properties of the proxy. This ensures that all accesses are be + * delegated to the backing instance from now on. */ + zend_object_dtor_dynamic_properties(obj); + obj->properties = NULL; + + for (int i = 0; i < Z_OBJ(retval)->ce->default_properties_count; i++) { + if (EXPECTED(Z_OBJ(retval)->ce->properties_info_table[i])) { + zend_object_dtor_property(obj, &obj->properties_table[i]); + ZVAL_UNDEF(&obj->properties_table[i]); + Z_PROP_FLAG_P(&obj->properties_table[i]) = IS_PROP_UNINIT | IS_PROP_LAZY; + } + } + + return Z_OBJ(retval); +} + +/* Initialize a lazy object. */ +ZEND_API zend_object *zend_lazy_object_init(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + + /* If obj is an initialized lazy proxy, return the real instance. This + * supports the following pattern: + * if (zend_lazy_object_must_init(obj)) { + * instance = zend_lazy_object_init(obj); + * } + */ + if (zend_lazy_object_initialized(obj)) { + ZEND_ASSERT(zend_object_is_lazy_proxy(obj)); + zend_lazy_object_info *info = zend_lazy_object_get_info(obj); + ZEND_ASSERT(info->flags & ZEND_LAZY_OBJECT_INITIALIZED); + return info->u.instance; + } + + zend_class_entry *ce = obj->ce; + + if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + } + + if (zend_object_is_lazy_proxy(obj)) { + return zend_lazy_object_init_proxy(obj); + } + + zend_fcall_info_cache *initializer = zend_lazy_object_get_initializer_fcc(obj); + + /* Prevent reentrant initialization */ + OBJ_EXTRA_FLAGS(obj) &= ~IS_OBJ_LAZY_UNINITIALIZED; + + /* Snapshot dynamic properties */ + HashTable *properties_snapshot = obj->properties; + if (properties_snapshot) { + GC_TRY_ADDREF(properties_snapshot); + } + + zval *properties_table_snapshot = NULL; + + /* Snapshot declared properties and initialize lazy properties to their + * default value */ + if (ce->default_properties_count) { + zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce); + zval *properties_table = obj->properties_table; + properties_table_snapshot = emalloc(sizeof(*properties_table_snapshot) * ce->default_properties_count); + + for (int i = 0; i < ce->default_properties_count; i++) { + ZVAL_COPY_PROP(&properties_table_snapshot[i], &properties_table[i]); + if (Z_PROP_FLAG_P(&properties_table[i]) & IS_PROP_LAZY) { + ZVAL_COPY_PROP(&properties_table[i], &default_properties_table[i]); + } + } + } + + /* Call initializer */ + zval retval; + int argc = 1; + zval zobj; + HashTable *named_params = NULL; + + ZVAL_OBJ(&zobj, obj); + + zend_call_known_fcc(initializer, &retval, argc, &zobj, named_params); + + if (EG(exception)) { + zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot); + return NULL; + } + + if (Z_TYPE(retval) != IS_NULL) { + zend_lazy_object_revert_init(obj, properties_table_snapshot, properties_snapshot); + zval_ptr_dtor(&retval); + zend_type_error("Lazy object initializer must return NULL or no value"); + return NULL; + } + + if (properties_table_snapshot) { + for (int i = 0; i < obj->ce->default_properties_count; i++) { + zval *p = &properties_table_snapshot[i]; + /* Use zval_ptr_dtor directly here (not zend_object_dtor_property), + * as any reference type_source will have already been deleted in + * case the prop is not bound to this value anymore. */ + i_zval_ptr_dtor(p); + } + efree(properties_table_snapshot); + } + + if (properties_snapshot) { + zend_release_properties(properties_snapshot); + } + + /* Must be very last in this function, for the + * zend_lazy_object_has_stale_info() check */ + zend_lazy_object_del_info(obj); + + return obj; +} + +/* Mark an object as non-lazy (after all properties were initialized) */ +void zend_lazy_object_realize(zend_object *obj) +{ + ZEND_ASSERT(zend_object_is_lazy(obj)); + ZEND_ASSERT(!zend_lazy_object_initialized(obj)); + + zend_lazy_object_del_info(obj); + +#if ZEND_DEBUG + for (int i = 0; i < obj->ce->default_properties_count; i++) { + ZEND_ASSERT(!(Z_PROP_FLAG_P(&obj->properties_table[i]) & IS_PROP_LAZY)); + } +#endif + + OBJ_EXTRA_FLAGS(obj) &= ~(IS_OBJ_LAZY_UNINITIALIZED | IS_OBJ_LAZY_PROXY); +} + +ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object) +{ + ZEND_ASSERT(zend_object_is_lazy(object)); + + zend_object *tmp = zend_lazy_object_init(object); + if (UNEXPECTED(!tmp)) { + ZEND_ASSERT(!object->properties || object->properties == &zend_empty_array); + return object->properties = (zend_array*) &zend_empty_array; + } + + object = tmp; + ZEND_ASSERT(!zend_lazy_object_must_init(object)); + + return zend_std_get_properties_ex(object); +} + +/* Initialize object and clone it. For proxies, we clone both the proxy and its + * real instance, and we don't call __clone() on the proxy. */ +zend_object *zend_lazy_object_clone(zend_object *old_obj) +{ + ZEND_ASSERT(zend_object_is_lazy(old_obj)); + + if (UNEXPECTED(!zend_lazy_object_initialized(old_obj) && !zend_lazy_object_init(old_obj))) { + ZEND_ASSERT(EG(exception)); + /* Clone handler must always return an object. It is discarded later due + * to the exception. */ + zval zv; + object_init_ex(&zv, old_obj->ce); + GC_ADD_FLAGS(Z_OBJ(zv), IS_OBJ_DESTRUCTOR_CALLED); + return Z_OBJ(zv); + } + + if (!zend_object_is_lazy_proxy(old_obj)) { + return zend_objects_clone_obj(old_obj); + } + + zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj); + zend_class_entry *ce = old_obj->ce; + zend_object *new_proxy = zend_objects_new(ce); + + for (int i = 0; i < ce->default_properties_count; i++) { + zval *p = &new_proxy->properties_table[i]; + ZVAL_UNDEF(p); + if (EXPECTED(ce->properties_info_table[i])) { + Z_PROP_FLAG_P(p) = IS_PROP_UNINIT | IS_PROP_LAZY; + } else { + Z_PROP_FLAG_P(p) = 0; + } + } + + OBJ_EXTRA_FLAGS(new_proxy) = OBJ_EXTRA_FLAGS(old_obj); + + zend_lazy_object_info *new_info = emalloc(sizeof(*info)); + *new_info = *info; + new_info->u.instance = zend_objects_clone_obj(info->u.instance); + + zend_lazy_object_set_info(new_proxy, new_info); + + return new_proxy; +} + +HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp) +{ + ZEND_ASSERT(zend_object_is_lazy(object)); + + if (zend_object_is_lazy_proxy(object)) { + if (zend_lazy_object_initialized(object)) { + HashTable *properties = zend_new_array(0); + zval instance; + ZVAL_OBJ(&instance, zend_lazy_object_get_instance(object)); + Z_ADDREF(instance); + zend_hash_str_add(properties, "instance", strlen("instance"), &instance); + *is_temp = 1; + return properties; + } + } + + *is_temp = 0; + return zend_get_properties_no_lazy_init(object); +} + +HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n) +{ + ZEND_ASSERT(zend_object_is_lazy(zobj)); + + zend_lazy_object_info *info = zend_lazy_object_get_info(zobj); + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + if (zend_lazy_object_initialized(zobj)) { + ZEND_ASSERT(zend_object_is_lazy_proxy(zobj)); + zend_get_gc_buffer_add_obj(gc_buffer, info->u.instance); + zend_get_gc_buffer_use(gc_buffer, table, n); + /* Initialized proxy object can not have properties */ + return NULL; + } + + zend_fcall_info_cache *fcc = &info->u.initializer.fcc; + if (fcc->object) { + zend_get_gc_buffer_add_obj(gc_buffer, fcc->object); + } + if (fcc->closure) { + zend_get_gc_buffer_add_obj(gc_buffer, fcc->closure); + } + zend_get_gc_buffer_add_zval(gc_buffer, &info->u.initializer.zv); + + /* Uninitialized lazy objects can not have dynamic properties, so we can + * ignore zobj->properties. */ + zval *prop = zobj->properties_table; + zval *end = prop + zobj->ce->default_properties_count; + for ( ; prop < end; prop++) { + zend_get_gc_buffer_add_zval(gc_buffer, prop); + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + return NULL; +} diff --git a/Zend/zend_lazy_objects.h b/Zend/zend_lazy_objects.h new file mode 100644 index 0000000000000..addb07173c3f4 --- /dev/null +++ b/Zend/zend_lazy_objects.h @@ -0,0 +1,106 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_LAZY_OBJECT_H +#define ZEND_LAZY_OBJECT_H + +#include "Zend/zend_types.h" + +/* Lazy object is a lazy proxy object */ +#define ZEND_LAZY_OBJECT_STRATEGY_PROXY (1<<0) + +/* Lazy object is a lazy ghost object */ +#define ZEND_LAZY_OBJECT_STRATEGY_GHOST (1<<1) + +/* Lazy object is initialized (info.u is an instance) */ +#define ZEND_LAZY_OBJECT_INITIALIZED (1<<2) + +/* Serialization skips initialization */ +#define ZEND_LAZY_OBJECT_SKIP_INITIALIZATION_ON_SERIALIZE (1<<3) + +/* Do not call destructor when making existing object lazy */ +#define ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR (1<<4) + +#define ZEND_LAZY_OBJECT_USER_MASK ( \ + ZEND_LAZY_OBJECT_SKIP_INITIALIZATION_ON_SERIALIZE | \ + ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR \ +) + +#define ZEND_LAZY_OBJECT_STRATEGY_MASK ( \ + ZEND_LAZY_OBJECT_STRATEGY_PROXY | \ + ZEND_LAZY_OBJECT_STRATEGY_GHOST \ +) + +typedef uint8_t zend_lazy_object_flags_t; + +typedef struct _zend_lazy_objects_store { + /* object handle -> *zend_lazy_object_info */ + HashTable infos; +} zend_lazy_objects_store; + +typedef struct _zend_fcall_info zend_fcall_info; +typedef struct _zend_fcall_info_cache zend_fcall_info_cache; + +ZEND_API bool zend_class_can_be_lazy(zend_class_entry *ce); +ZEND_API zend_object *zend_object_make_lazy(zend_object *obj, + zend_class_entry *class_type, zval *initializer_zv, + zend_fcall_info_cache *initializer_fcc, zend_lazy_object_flags_t flags); +ZEND_API zend_object *zend_lazy_object_init(zend_object *obj); +ZEND_API zend_object *zend_lazy_object_mark_as_initialized(zend_object *obj); + +void zend_lazy_objects_init(zend_lazy_objects_store *store); +void zend_lazy_objects_destroy(zend_lazy_objects_store *store); +zval* zend_lazy_object_get_initializer_zv(zend_object *obj); +zend_object *zend_lazy_object_get_instance(zend_object *obj); +zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj); +void zend_lazy_object_del_info(zend_object *obj); +ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object); +zend_object *zend_lazy_object_clone(zend_object *old_obj); +HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp); +HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n); +bool zend_lazy_object_decr_lazy_props(zend_object *obj); +void zend_lazy_object_realize(zend_object *obj); + +static zend_always_inline bool zend_object_is_lazy(zend_object *obj) +{ + return (OBJ_EXTRA_FLAGS(obj) & (IS_OBJ_LAZY_UNINITIALIZED | IS_OBJ_LAZY_PROXY)); +} + +static zend_always_inline bool zend_object_is_lazy_proxy(zend_object *obj) +{ + return (OBJ_EXTRA_FLAGS(obj) & IS_OBJ_LAZY_PROXY); +} + +static zend_always_inline bool zend_lazy_object_initialized(zend_object *obj) +{ + return !(OBJ_EXTRA_FLAGS(obj) & IS_OBJ_LAZY_UNINITIALIZED); +} + +/* True if accessing a lazy prop on obj mandates a call to + * zend_lazy_object_init() */ +static zend_always_inline bool zend_lazy_object_must_init(zend_object *obj) +{ + return zend_object_is_lazy(obj); +} + +static inline bool zend_lazy_object_initialize_on_serialize(zend_object *obj) +{ + return !(zend_lazy_object_get_flags(obj) & ZEND_LAZY_OBJECT_SKIP_INITIALIZATION_ON_SERIALIZE); +} + +#endif /* ZEND_LAZY_OBJECT_H */ diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index fdac24ccb258b..8a973f4fc0927 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -20,6 +20,7 @@ #include "zend.h" #include "zend_globals.h" +#include "zend_lazy_objects.h" #include "zend_variables.h" #include "zend_API.h" #include "zend_objects.h" @@ -92,6 +93,7 @@ ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj) /* {{{ } /* }}} */ +/* Implements the fast path for array cast */ ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj) /* {{{ */ { zend_property_info *prop_info; @@ -100,6 +102,7 @@ ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj) /* zval* prop; int i; + ZEND_ASSERT(!(zend_object_is_lazy_proxy(zobj) && zend_lazy_object_initialized(zobj))); ZEND_ASSERT(!zobj->properties); ht = zend_new_array(ce->default_properties_count); if (ce->default_properties_count) { @@ -130,13 +133,31 @@ ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj) /* ZEND_API HashTable *zend_std_get_properties(zend_object *zobj) /* {{{ */ { - if (!zobj->properties) { - return rebuild_object_properties_internal(zobj); - } - return zobj->properties; + return zend_std_get_properties_ex(zobj); } /* }}} */ +/* Fetch properties HashTable without triggering lazy initialization */ +ZEND_API HashTable *zend_get_properties_no_lazy_init(zend_object *zobj) +{ + if (zobj->handlers->get_properties == zend_std_get_properties) { + if (UNEXPECTED(zend_object_is_lazy_proxy(zobj) + && zend_lazy_object_initialized(zobj))) { + zend_object *instance = zend_lazy_object_get_instance(zobj); + return zend_get_properties_no_lazy_init(instance); + } + + if (!zobj->properties) { + rebuild_object_properties_internal(zobj); + } + return zobj->properties; + } + + ZEND_ASSERT(!zend_object_is_lazy(zobj)); + + return zobj->handlers->get_properties(zobj); +} + ZEND_API HashTable *zend_std_get_gc(zend_object *zobj, zval **table, int *n) /* {{{ */ { if (zobj->handlers->get_properties != zend_std_get_properties) { @@ -144,7 +165,9 @@ ZEND_API HashTable *zend_std_get_gc(zend_object *zobj, zval **table, int *n) /* *n = 0; return zobj->handlers->get_properties(zobj); } else { - if (zobj->properties) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + return zend_lazy_object_get_gc(zobj, table, n); + } else if (zobj->properties) { *table = NULL; *n = 0; return zobj->properties; @@ -164,6 +187,10 @@ ZEND_API HashTable *zend_std_get_debug_info(zend_object *object, int *is_temp) / HashTable *ht; if (!ce->__debugInfo) { + if (UNEXPECTED(zend_object_is_lazy(object))) { + return zend_lazy_object_debug_info(object, is_temp); + } + *is_temp = 0; return object->handlers->get_properties(object); } @@ -826,6 +853,8 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int goto exit; } + retval = &EG(uninitialized_zval); + /* magic isset */ if ((type == BP_VAR_IS) && zobj->ce->__isset) { zval tmp_result; @@ -894,6 +923,17 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int } uninit_error: + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + if (!prop_info || (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY)) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + retval = &EG(uninitialized_zval); + goto exit; + } + + return zend_std_read_property(zobj, name, type, cache_slot, rv); + } + } if (type != BP_VAR_IS) { if (prop_info) { zend_typed_property_uninitialized_access(prop_info, name); @@ -1005,6 +1045,11 @@ found:; goto exit; } if (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + if (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_LAZY) { + goto lazy_init; + } + } /* Writes to uninitialized typed properties bypass __set(). */ goto write_std_property; } @@ -1082,6 +1127,10 @@ found:; OBJ_RELEASE(zobj); variable_ptr = value; } else if (EXPECTED(!IS_WRONG_PROPERTY_OFFSET(property_offset))) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + goto lazy_init; + } + goto write_std_property; } else { /* Trigger the correct error */ @@ -1092,6 +1141,9 @@ found:; } } else { ZEND_ASSERT(!IS_WRONG_PROPERTY_OFFSET(property_offset)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + goto lazy_init; + } write_std_property: if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { variable_ptr = OBJ_PROP(zobj, property_offset); @@ -1116,12 +1168,20 @@ found:; } Z_TRY_ADDREF_P(value); - variable_ptr = zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + variable_ptr = zend_hash_add_new(zend_std_get_properties(zobj), name, value); } } exit: return variable_ptr; + +lazy_init: + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(!zobj)) { + variable_ptr = &EG(error_zval); + goto exit; + } + return zend_std_write_property(zobj, name, value, cache_slot); } /* }}} */ @@ -1252,6 +1312,14 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam if (EXPECTED(!zobj->ce->__get) || UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET) || UNEXPECTED(prop_info && (Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT))) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + return &EG(error_zval); + } + + return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot); + } if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) { if (prop_info) { zend_typed_property_uninitialized_access(prop_info, name); @@ -1302,6 +1370,14 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam return &EG(error_zval); } } + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + return &EG(error_zval); + } + + return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot); + } if (UNEXPECTED(!zobj->properties)) { rebuild_object_properties_internal(zobj); } @@ -1368,6 +1444,15 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void return; } if (UNEXPECTED(Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT)) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj) && (Z_PROP_FLAG_P(slot) & IS_PROP_LAZY))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + return; + } + zend_std_unset_property(zobj, name, cache_slot); + return; + } + /* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */ Z_PROP_FLAG_P(slot) = 0; return; @@ -1401,6 +1486,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void (*guard) |= IN_UNSET; /* prevent circular unsetting */ zend_std_call_unsetter(zobj, name); (*guard) &= ~IN_UNSET; + return; } else if (UNEXPECTED(IS_WRONG_PROPERTY_OFFSET(property_offset))) { /* Trigger the correct error */ zend_wrong_offset(zobj->ce, name); @@ -1410,6 +1496,15 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void /* Nothing to do: The property already does not exist. */ } } + + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + return; + } + zend_std_unset_property(zobj, name, cache_slot); + return; + } } /* }}} */ @@ -1977,7 +2072,8 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ if (zobj1->ce != zobj2->ce) { return ZEND_UNCOMPARABLE; /* different classes */ } - if (!zobj1->properties && !zobj2->properties) { + if (!zobj1->properties && !zobj2->properties + && !zend_object_is_lazy(zobj1) && !zend_object_is_lazy(zobj2)) { zend_property_info *info; int i; @@ -2062,8 +2158,7 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has } if (UNEXPECTED(Z_PROP_FLAG_P(value) & IS_PROP_UNINIT)) { /* Skip __isset() for uninitialized typed properties */ - result = false; - goto exit; + goto lazy_init; } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) { if (EXPECTED(zobj->properties != NULL)) { @@ -2146,6 +2241,10 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has goto exit; } + if (!zobj->ce->__isset) { + goto lazy_init; + } + result = false; if ((has_set_exists != ZEND_PROPERTY_EXISTS) && zobj->ce->__isset) { uint32_t *guard = zend_get_property_guard(zobj, name); @@ -2177,6 +2276,22 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has exit: return result; + +lazy_init: + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + if (!value || (Z_PROP_FLAG_P(value) & IS_PROP_LAZY)) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + result = 0; + goto exit; + } + + return zend_std_has_property(zobj, name, has_set_exists, cache_slot); + } + } + + result = 0; + goto exit; } /* }}} */ @@ -2259,14 +2374,29 @@ ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purp if (obj->ce->num_hooked_props) { return zend_hooked_object_build_properties(obj); } - ZEND_FALLTHROUGH; - case ZEND_PROP_PURPOSE_ARRAY_CAST: - case ZEND_PROP_PURPOSE_SERIALIZE: ht = obj->handlers->get_properties(obj); if (ht) { GC_TRY_ADDREF(ht); } return ht; + case ZEND_PROP_PURPOSE_ARRAY_CAST: + ht = zend_get_properties_no_lazy_init(obj); + if (ht) { + GC_TRY_ADDREF(ht); + } + return ht; + case ZEND_PROP_PURPOSE_SERIALIZE: { + if (zend_object_is_lazy(obj) + && !zend_lazy_object_initialize_on_serialize(obj)) { + ht = zend_get_properties_no_lazy_init(obj); + } else { + ht = obj->handlers->get_properties(obj); + } + if (ht) { + GC_TRY_ADDREF(ht); + } + return ht; + } default: ZEND_UNREACHABLE(); return NULL; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 8ec2164413df5..f36eb765c24a5 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -22,8 +22,10 @@ #include +#include "zend_hash.h" #include "zend_types.h" #include "zend_property_hooks.h" +#include "zend_lazy_objects.h" struct _zend_property_info; @@ -251,6 +253,7 @@ ZEND_API ZEND_COLD bool zend_std_unset_static_property(zend_class_entry *ce, zen ZEND_API zend_function *zend_std_get_constructor(zend_object *object); ZEND_API struct _zend_property_info *zend_get_property_info(const zend_class_entry *ce, zend_string *member, int silent); ZEND_API HashTable *zend_std_get_properties(zend_object *object); +ZEND_API HashTable *zend_get_properties_no_lazy_init(zend_object *zobj); ZEND_API HashTable *zend_std_get_gc(zend_object *object, zval **table, int *n); ZEND_API HashTable *zend_std_get_debug_info(zend_object *object, int *is_temp); ZEND_API zend_result zend_std_cast_object_tostring(zend_object *object, zval *writeobj, int type); @@ -272,14 +275,31 @@ ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) { + if (UNEXPECTED(zend_lazy_object_must_init(object))) { + return zend_lazy_object_get_properties(object); + } if (!object->properties) { return rebuild_object_properties_internal(object); } return object->properties; } +/* Implements the fast path for array cast */ ZEND_API HashTable *zend_std_build_object_properties_array(zend_object *zobj); +#define ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(object) ( \ + /* We can use zend_std_build_object_properties_array() for objects \ + * without properties ht and with standard handlers */ \ + Z_OBJ_P(object)->properties == NULL \ + && Z_OBJ_HT_P(object)->get_properties_for == NULL \ + && Z_OBJ_HT_P(object)->get_properties == zend_std_get_properties \ + /* For initialized proxies we need to forward to the real instance */ \ + && ( \ + !zend_object_is_lazy_proxy(Z_OBJ_P(object)) \ + || !zend_lazy_object_initialized(Z_OBJ_P(object)) \ + ) \ +) + /* Handler for objects that cannot be meaningfully compared. * Only objects with the same identity will be considered equal. */ ZEND_API int zend_objects_not_comparable(zval *o1, zval *o2); diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index af4d1f265897a..348b3f3416b91 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -25,12 +25,14 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_weakrefs.h" +#include "zend_lazy_objects.h" static zend_always_inline void _zend_object_std_init(zend_object *object, zend_class_entry *ce) { GC_SET_REFCOUNT(object, 1); GC_TYPE_INFO(object) = GC_OBJECT; object->ce = ce; + object->extra_flags = 0; object->handlers = ce->default_object_handlers; object->properties = NULL; zend_objects_store_put(object); @@ -46,14 +48,8 @@ ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class _zend_object_std_init(object, ce); } -ZEND_API void zend_object_std_dtor(zend_object *object) +void zend_object_dtor_dynamic_properties(zend_object *object) { - zval *p, *end; - - if (UNEXPECTED(GC_FLAGS(object) & IS_OBJ_WEAKLY_REFERENCED)) { - zend_weakrefs_notify(object); - } - if (object->properties) { if (EXPECTED(!(GC_FLAGS(object->properties) & IS_ARRAY_IMMUTABLE))) { if (EXPECTED(GC_DELREF(object->properties) == 0) @@ -62,20 +58,41 @@ ZEND_API void zend_object_std_dtor(zend_object *object) } } } +} + +void zend_object_dtor_property(zend_object *object, zval *p) +{ + if (Z_REFCOUNTED_P(p)) { + if (UNEXPECTED(Z_ISREF_P(p)) && + (ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(p)))) { + zend_property_info *prop_info = zend_get_property_info_for_slot(object, p); + if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(p), prop_info); + } + } + i_zval_ptr_dtor(p); + } +} + +ZEND_API void zend_object_std_dtor(zend_object *object) +{ + zval *p, *end; + + if (UNEXPECTED(GC_FLAGS(object) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakrefs_notify(object); + } + + if (UNEXPECTED(zend_object_is_lazy(object))) { + zend_lazy_object_del_info(object); + } + + zend_object_dtor_dynamic_properties(object); + p = object->properties_table; if (EXPECTED(object->ce->default_properties_count)) { end = p + object->ce->default_properties_count; do { - if (Z_REFCOUNTED_P(p)) { - if (UNEXPECTED(Z_ISREF_P(p)) && - (ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(p)))) { - zend_property_info *prop_info = zend_get_property_info_for_slot(object, p); - if (ZEND_TYPE_IS_SET(prop_info->type)) { - ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(p), prop_info); - } - } - i_zval_ptr_dtor(p); - } + zend_object_dtor_property(object, p); p++; } while (p != end); } @@ -99,6 +116,10 @@ ZEND_API void zend_objects_destroy_object(zend_object *object) zend_function *destructor = object->ce->destructor; if (destructor) { + if (UNEXPECTED(zend_object_is_lazy(object))) { + return; + } + zend_object *old_exception; const zend_op *old_opline_before_exception; @@ -286,6 +307,10 @@ ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object) { zend_object *new_object; + if (UNEXPECTED(zend_object_is_lazy(old_object))) { + return zend_lazy_object_clone(old_object); + } + /* assume that create isn't overwritten, so when clone depends on the * overwritten one then it must itself be overwritten */ new_object = zend_objects_new(old_object->ce); diff --git a/Zend/zend_objects.h b/Zend/zend_objects.h index 91d388154dd13..41e3bcd9594b1 100644 --- a/Zend/zend_objects.h +++ b/Zend/zend_objects.h @@ -30,6 +30,10 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, ZEND_API void zend_object_std_dtor(zend_object *object); ZEND_API void zend_objects_destroy_object(zend_object *object); ZEND_API zend_object *zend_objects_clone_obj(zend_object *object); + +void zend_object_dtor_dynamic_properties(zend_object *object); +void zend_object_dtor_property(zend_object *object, zval *p); + END_EXTERN_C() #endif /* ZEND_OBJECTS_H */ diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 5331244731cac..ae75e95a71c1d 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -812,9 +812,7 @@ ZEND_API void ZEND_FASTCALL convert_to_array(zval *op) /* {{{ */ case IS_OBJECT: if (Z_OBJCE_P(op) == zend_ce_closure) { convert_scalar_to_array(op); - } else if (Z_OBJ_P(op)->properties == NULL - && Z_OBJ_HT_P(op)->get_properties_for == NULL - && Z_OBJ_HT_P(op)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(op)) { /* Optimized version without rebuilding properties HashTable */ HashTable *ht = zend_std_build_object_properties_array(Z_OBJ_P(op)); OBJ_RELEASE(Z_OBJ_P(op)); diff --git a/Zend/zend_property_hooks.c b/Zend/zend_property_hooks.c index b00a526125c27..9cfa15ecafa4d 100644 --- a/Zend/zend_property_hooks.c +++ b/Zend/zend_property_hooks.c @@ -18,6 +18,8 @@ #include "zend.h" #include "zend_API.h" +#include "zend_hash.h" +#include "zend_lazy_objects.h" #include "zend_property_hooks.h" typedef struct { @@ -49,6 +51,13 @@ static uint32_t zho_num_backed_props(zend_object *zobj) static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, bool include_dynamic_props) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(!zobj)) { + return (zend_array*) &zend_empty_array; + } + } + zend_class_entry *ce = zobj->ce; zend_array *properties = zend_new_array(ce->default_properties_count); zend_hash_real_init_mixed(properties); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index d33f8a33bcbe6..8f012868ddab4 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -556,6 +556,7 @@ typedef struct _HashTableIterator { struct _zend_object { zend_refcounted_h gc; uint32_t handle; // TODO: may be removed ??? + uint32_t extra_flags; /* OBJ_EXTRA_FLAGS() */ zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; @@ -829,6 +830,13 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define OBJ_FLAGS(obj) GC_FLAGS(obj) +/* object extra flags (zend_object.flags) */ + +#define IS_OBJ_LAZY_UNINITIALIZED (1U<<31) /* Virtual proxy or uninitialized Ghost */ +#define IS_OBJ_LAZY_PROXY (1U<<30) /* Virtual proxy (may be initialized) */ + +#define OBJ_EXTRA_FLAGS(obj) ((obj)->extra_flags) + /* Fast class cache */ #define ZSTR_HAS_CE_CACHE(s) (GC_FLAGS(s) & IS_STR_CLASS_NAME_MAP_PTR) #define ZSTR_GET_CE_CACHE(s) ZSTR_GET_CE_CACHE_EX(s, 1) @@ -1556,6 +1564,7 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) { * macros for this purpose, so this workaround is easier to remove in the future. */ #define IS_PROP_UNINIT (1<<0) #define IS_PROP_REINITABLE (1<<1) /* It has impact only on readonly properties */ +#define IS_PROP_LAZY (1<<2) #define Z_PROP_FLAG_P(z) Z_EXTRA_P(z) #define ZVAL_COPY_VALUE_PROP(z, v) \ do { *(z) = *(v); } while (0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 55fdb7d46582b..b46952a81718b 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2481,6 +2481,16 @@ ZEND_VM_C_LABEL(fast_assign_obj): } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(GET_OP2_ZVAL_PTR(BP_VAR_R)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + ZEND_VM_C_GOTO(free_and_exit_assign_obj); + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -2519,7 +2529,7 @@ ZEND_VM_C_LABEL(fast_assign_obj): Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -6429,9 +6439,7 @@ ZEND_VM_COLD_CONST_HANDLER(51, ZEND_CAST, CONST|TMP|VAR|CV, ANY, TYPE) } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_P(expr)->properties == NULL - && Z_OBJ_HT_P(expr)->get_properties_for == NULL - && Z_OBJ_HT_P(expr)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { /* Optimized version without rebuilding properties HashTable */ ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); } else { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 16455b6e0cd58..6738674667e74 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5172,9 +5172,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CONST_H } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_P(expr)->properties == NULL - && Z_OBJ_HT_P(expr)->get_properties_for == NULL - && Z_OBJ_HT_P(expr)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { /* Optimized version without rebuilding properties HashTable */ ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); } else { @@ -20045,9 +20043,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_TMP_HANDLER(ZEND_OPC } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_P(expr)->properties == NULL - && Z_OBJ_HT_P(expr)->get_properties_for == NULL - && Z_OBJ_HT_P(expr)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { /* Optimized version without rebuilding properties HashTable */ ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); } else { @@ -22698,9 +22694,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_VAR_HANDLER(ZEND_OPC } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_P(expr)->properties == NULL - && Z_OBJ_HT_P(expr)->get_properties_for == NULL - && Z_OBJ_HT_P(expr)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { /* Optimized version without rebuilding properties HashTable */ ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); } else { @@ -23981,6 +23975,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -24019,7 +24023,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -24125,6 +24129,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -24163,7 +24177,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -24269,6 +24283,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -24307,7 +24331,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -24413,6 +24437,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -24451,7 +24485,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -26943,6 +26977,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -26981,7 +27025,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -27087,6 +27131,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -27125,7 +27179,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -27231,6 +27285,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -27269,7 +27333,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -27375,6 +27439,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -27413,7 +27487,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -31267,6 +31341,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -31305,7 +31389,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -31411,6 +31495,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -31449,7 +31543,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -31555,6 +31649,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -31593,7 +31697,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -31699,6 +31803,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -31737,7 +31851,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -33985,6 +34099,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -34023,7 +34147,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -34129,6 +34253,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -34167,7 +34301,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -34273,6 +34407,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -34311,7 +34455,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -34417,6 +34561,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -34455,7 +34609,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -36124,6 +36278,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -36162,7 +36326,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -36268,6 +36432,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -36306,7 +36480,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -36412,6 +36586,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -36450,7 +36634,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -36556,6 +36740,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -36594,7 +36788,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -38741,6 +38935,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -38779,7 +38983,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -38885,6 +39089,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -38923,7 +39137,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -39029,6 +39243,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -39067,7 +39291,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -39173,6 +39397,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -39211,7 +39445,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -40743,9 +40977,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CAST_SPEC_CV_HANDLER(ZEND_OPCO } else { ZVAL_EMPTY_ARRAY(result); } - } else if (Z_OBJ_P(expr)->properties == NULL - && Z_OBJ_HT_P(expr)->get_properties_for == NULL - && Z_OBJ_HT_P(expr)->get_properties == zend_std_get_properties) { + } else if (ZEND_STD_BUILD_OBJECT_PROPERTIES_ARRAY_COMPATIBLE(expr)) { /* Optimized version without rebuilding properties HashTable */ ZVAL_ARR(result, zend_std_build_object_properties_array(Z_OBJ_P(expr))); } else { @@ -43097,6 +43329,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -43135,7 +43377,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -43241,6 +43483,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -43279,7 +43531,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -43385,6 +43637,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -43423,7 +43685,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -43529,6 +43791,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(RT_CONSTANT(opline, opline->op2)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -43567,7 +43839,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -47020,6 +47292,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -47058,7 +47340,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -47164,6 +47446,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -47202,7 +47494,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -47308,6 +47600,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -47346,7 +47648,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -47452,6 +47754,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -47490,7 +47802,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -52490,6 +52802,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -52528,7 +52850,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -52634,6 +52956,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -52672,7 +53004,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -52778,6 +53110,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -52816,7 +53158,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } @@ -52922,6 +53264,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(prop_offset))) { name = Z_STR_P(_get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC)); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (!zobj) { + value = &EG(uninitialized_zval); + goto free_and_exit_assign_obj; + } + } + if (!zobj->ce->__set && (zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES)) { + rebuild_object_properties_internal(zobj); + } if (EXPECTED(zobj->properties != NULL)) { if (UNEXPECTED(GC_REFCOUNT(zobj->properties) > 1)) { if (EXPECTED(!(GC_FLAGS(zobj->properties) & IS_ARRAY_IMMUTABLE))) { @@ -52960,7 +53312,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ Z_TRY_ADDREF_P(value); } } - zend_hash_add_new(zend_std_get_properties_ex(zobj), name, value); + zend_hash_add_new(zobj->properties, name, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), value); } diff --git a/configure.ac b/configure.ac index 2693ba09f4af9..6a30788a2afce 100644 --- a/configure.ac +++ b/configure.ac @@ -1743,6 +1743,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_iterators.c zend_language_parser.c zend_language_scanner.c + zend_lazy_objects.c zend_list.c zend_llist.c zend_max_execution_timer.c diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index d53427523f4bc..6d9216b6f7f3f 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -246,6 +246,7 @@ static zend_always_inline void zend_ffi_object_init(zend_object *object, zend_cl { GC_SET_REFCOUNT(object, 1); GC_TYPE_INFO(object) = GC_OBJECT | (IS_OBJ_DESTRUCTOR_CALLED << GC_FLAGS_SHIFT); + object->extra_flags = 0; object->ce = ce; object->handlers = ce->default_object_handlers; object->properties = NULL; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 1e344cde436ff..de3106601b9c5 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -28,6 +28,7 @@ #include #include "zend_enum.h" #include "zend_property_hooks.h" +#include "zend_lazy_objects.h" static const char digits[] = "0123456789abcdef"; @@ -124,7 +125,8 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, } else if (Z_OBJ_P(val)->properties == NULL && Z_OBJ_HT_P(val)->get_properties_for == NULL && Z_OBJ_HT_P(val)->get_properties == zend_std_get_properties - && Z_OBJ_P(val)->ce->num_hooked_props == 0) { + && Z_OBJ_P(val)->ce->num_hooked_props == 0 + && !zend_object_is_lazy(Z_OBJ_P(val))) { /* Optimized version without rebuilding properties HashTable */ zend_object *obj = Z_OBJ_P(val); zend_class_entry *ce = obj->ce; diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 49ddf40a0e7ce..9b1bf9a71dde1 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14466,22 +14466,20 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ZEND_ASSERT(slow_inputs == IR_UNUSED); goto slow_path; } - if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set) { - // Undefined property with magic __get()/__set() - if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { - int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); - const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + // Undefined property with potential magic __get()/__set() or lazy object + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) { + int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); - if (!exit_addr) { - return 0; - } - ir_GUARD(jit_Z_TYPE_INFO(jit, prop_addr), ir_CONST_ADDR(exit_addr)); - } else { - ir_ref if_def = ir_IF(jit_Z_TYPE_INFO(jit, prop_addr)); - ir_IF_FALSE_cold(if_def); - ir_END_list(slow_inputs); - ir_IF_TRUE(if_def); + if (!exit_addr) { + return 0; } + ir_GUARD(jit_Z_TYPE_INFO(jit, prop_addr), ir_CONST_ADDR(exit_addr)); + } else { + ir_ref if_def = ir_IF(jit_Z_TYPE_INFO(jit, prop_addr)); + ir_IF_FALSE_cold(if_def); + ir_END_list(slow_inputs); + ir_IF_TRUE(if_def); } if (ZEND_TYPE_IS_SET(prop_info->type)) { ir_ref ref, arg3, arg4; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 1215fa8b1ad48..531db004b3ba6 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -19,7 +19,11 @@ */ #include "zend_compile.h" +#include "zend_execute.h" +#include "zend_lazy_objects.h" +#include "zend_object_handlers.h" #include "zend_type_info.h" +#include "zend_types.h" #ifdef HAVE_CONFIG_H #include #endif @@ -474,7 +478,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char smart_str_append_printf(str, "%s }\n", indent); if (obj && Z_TYPE_P(obj) == IS_OBJECT) { - HashTable *properties = Z_OBJ_HT_P(obj)->get_properties(Z_OBJ_P(obj)); + HashTable *properties = zend_get_properties_no_lazy_init(Z_OBJ_P(obj)); zend_string *prop_name; smart_str prop_str = {0}; @@ -5187,6 +5191,201 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs) } /* }}} */ +void reflection_class_new_lazy(INTERNAL_FUNCTION_PARAMETERS, + int strategy, bool is_reset) +{ + reflection_object *intern; + zend_object *obj; + zend_class_entry *ce; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_long options = 0; + + ZEND_ASSERT(strategy == ZEND_LAZY_OBJECT_STRATEGY_GHOST + || strategy == ZEND_LAZY_OBJECT_STRATEGY_PROXY); + + GET_REFLECTION_OBJECT_PTR(ce); + + if (is_reset) { + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_OBJ_OF_CLASS(obj, ce) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(options) + ZEND_PARSE_PARAMETERS_END(); + } else { + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(options) + ZEND_PARSE_PARAMETERS_END(); + obj = NULL; + } + + if (options & ~ZEND_LAZY_OBJECT_USER_MASK) { + uint32_t arg_num = 2 + is_reset; + zend_argument_error(reflection_exception_ptr, arg_num, + "contains invalid flags"); + RETURN_THROWS(); + } + + if (!is_reset && (options & ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR)) { + zend_argument_error(reflection_exception_ptr, 2, + "does not accept ReflectionClass::SKIP_DESTRUCTOR"); + RETURN_THROWS(); + } + + if (is_reset) { + if (zend_object_is_lazy(obj) && !zend_lazy_object_initialized(obj)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Object is already lazy"); + RETURN_THROWS(); + } + } else { + obj = NULL; + } + + if (!fcc.function_handler) { + /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal + * 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); + } + + obj = zend_object_make_lazy(obj, ce, &fci.function_name, &fcc, + strategy | options); + + if (!obj) { + RETURN_THROWS(); + } + + if (!is_reset) { + RETURN_OBJ(obj); + } +} + +/* {{{ Instantiates a lazy instance, using the ghost strategy */ +PHP_METHOD(ReflectionClass, newLazyGhost) +{ + reflection_class_new_lazy(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_LAZY_OBJECT_STRATEGY_GHOST, /* is_reset */ false); +} +/* }}} */ + +/* {{{ Instantiates a lazy instance, using the proxy strategy */ +PHP_METHOD(ReflectionClass, newLazyProxy) +{ + reflection_class_new_lazy(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_LAZY_OBJECT_STRATEGY_PROXY, /* is_reset */ false); +} +/* }}} */ + +/* {{{ Reset an object and make it lazy, using the ghost strategy */ +PHP_METHOD(ReflectionClass, resetAsLazyGhost) +{ + reflection_class_new_lazy(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_LAZY_OBJECT_STRATEGY_GHOST, /* is_reset */ true); +} +/* }}} */ + +/* {{{ Reset an object and make it lazy, using the proxy strategy */ +PHP_METHOD(ReflectionClass, resetAsLazyProxy) +{ + reflection_class_new_lazy(INTERNAL_FUNCTION_PARAM_PASSTHRU, + ZEND_LAZY_OBJECT_STRATEGY_PROXY, /* is_reset */ true); +} +/* }}} */ + +/* {{{ Returns whether object lazy and uninitialized */ +ZEND_METHOD(ReflectionClass, isUninitializedLazyObject) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ce); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJ_OF_CLASS(object, ce) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(zend_object_is_lazy(object) && !zend_lazy_object_initialized(object)); +} +/* }}} */ + +/* {{{ Trigger object initialization */ +ZEND_METHOD(ReflectionClass, initializeLazyObject) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ce); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJ_OF_CLASS(object, ce) + ZEND_PARSE_PARAMETERS_END(); + + if (zend_object_is_lazy(object) + && !zend_lazy_object_initialized(object)) { + zend_lazy_object_init(object); + } + + if (zend_lazy_object_initialized(object)) { + RETURN_OBJ_COPY(zend_lazy_object_get_instance(object)); + } else { + RETURN_THROWS(); + } +} +/* }}} */ + +/* {{{ Mark object as initialized without calling the initializer */ +ZEND_METHOD(ReflectionClass, markLazyObjectAsInitialized) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ce); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJ_OF_CLASS(object, ce) + ZEND_PARSE_PARAMETERS_END(); + + if (zend_object_is_lazy(object) + && !zend_lazy_object_initialized(object)) { + zend_lazy_object_mark_as_initialized(object); + } + + if (zend_lazy_object_initialized(object)) { + RETURN_OBJ_COPY(zend_lazy_object_get_instance(object)); + } else { + RETURN_THROWS(); + } +} +/* }}} */ + +/* {{{ Get lazy object initializer */ +ZEND_METHOD(ReflectionClass, getLazyInitializer) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_object *object; + + GET_REFLECTION_OBJECT_PTR(ce); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJ_OF_CLASS(object, ce) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_object_is_lazy(object) + || zend_lazy_object_initialized(object)) { + RETURN_NULL(); + } + + RETURN_ZVAL(zend_lazy_object_get_initializer_zv(object), 1, 0); +} +/* }}} */ + /* {{{ Returns an array of interfaces this class implements */ ZEND_METHOD(ReflectionClass, getInterfaces) { @@ -5890,6 +6089,16 @@ ZEND_METHOD(ReflectionProperty, getRawValue) } } +static void reflection_property_set_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *value) +{ + if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_update_property_ex(intern->ce, object, ref->unmangled_name, value); + } else { + zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_SET, ref->unmangled_name); + zend_call_known_instance_method_with_1_params(func, object, NULL, value); + } +} + ZEND_METHOD(ReflectionProperty, setRawValue) { reflection_object *intern; @@ -5908,11 +6117,133 @@ ZEND_METHOD(ReflectionProperty, setRawValue) RETURN_THROWS(); } - if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { - zend_update_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, value); - } else { - zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_SET, ref->unmangled_name); - zend_call_known_instance_method_with_1_params(func, Z_OBJ_P(object), NULL, value); + reflection_property_set_raw_value(ref, intern, Z_OBJ_P(object), value); +} + +static zend_result reflection_property_check_lazy_compatible(reflection_object *intern, + property_reference *ref, zend_object *object, const char *method) +{ + if (!ref->prop) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Can not use %s on dynamic property %s::$%s", + method, ZSTR_VAL(intern->ce->name), + ZSTR_VAL(ref->unmangled_name)); + return FAILURE; + } + + if (ref->prop->flags & ZEND_ACC_STATIC) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Can not use %s on static property %s::$%s", + method, ZSTR_VAL(intern->ce->name), + ZSTR_VAL(ref->unmangled_name)); + return FAILURE; + } + + if (ref->prop->flags & ZEND_ACC_VIRTUAL) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Can not use %s on virtual property %s::$%s", + method, ZSTR_VAL(intern->ce->name), + ZSTR_VAL(ref->unmangled_name)); + return FAILURE; + } + + if (UNEXPECTED(object->handlers->write_property != zend_std_write_property)) { + if (!zend_class_can_be_lazy(object->ce)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Can not use %s on internal class %s", + method, ZSTR_VAL(intern->ce->name)); + return FAILURE; + } + } + + ZEND_ASSERT(IS_VALID_PROPERTY_OFFSET(ref->prop->offset)); + + return SUCCESS; +} + +/* {{{ Set property value withtout triggering initializer while skipping hooks if any */ +ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization) +{ + reflection_object *intern; + property_reference *ref; + zend_object *object; + zval *value; + + GET_REFLECTION_OBJECT_PTR(ref); + + ZEND_PARSE_PARAMETERS_START(2, 2) { + Z_PARAM_OBJ_OF_CLASS(object, intern->ce) + Z_PARAM_ZVAL(value) + } ZEND_PARSE_PARAMETERS_END(); + + if (reflection_property_check_lazy_compatible(intern, ref, object, + "setRawValueWithoutLazyInitialization") == FAILURE) { + RETURN_THROWS(); + } + + zval *var_ptr = OBJ_PROP(object, ref->prop->offset); + bool prop_was_lazy = Z_PROP_FLAG_P(var_ptr) & IS_PROP_LAZY; + + /* Do not trigger initialization */ + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_LAZY; + + reflection_property_set_raw_value(ref, intern, object, value); + + /* Mark property as lazy again if an exception prevented update */ + if (EG(exception) && prop_was_lazy && Z_TYPE_P(var_ptr) == IS_UNDEF + && zend_object_is_lazy(object) + && !zend_lazy_object_initialized(object)) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_LAZY; + } + + /* Object becomes non-lazy if this was the last lazy prop */ + if (prop_was_lazy && !(Z_PROP_FLAG_P(var_ptr) & IS_PROP_LAZY) + && zend_object_is_lazy(object) + && !zend_lazy_object_initialized(object)) { + if (zend_lazy_object_decr_lazy_props(object)) { + zend_lazy_object_realize(object); + } + } +} + +/* {{{ Mark property as non-lazy, and initialize to default value */ +ZEND_METHOD(ReflectionProperty, skipLazyInitialization) +{ + 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 (reflection_property_check_lazy_compatible(intern, ref, object, + "skipLazyInitialization") == FAILURE) { + RETURN_THROWS(); + } + + bool prop_was_lazy = (Z_PROP_FLAG_P(OBJ_PROP(object, ref->prop->offset)) & IS_PROP_LAZY); + + zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(ref->prop->offset)]; + zval *dst = OBJ_PROP(object, ref->prop->offset); + + if (!(Z_PROP_FLAG_P(dst) & IS_PROP_LAZY)) { + /* skipLazyInitialization has no effect on non-lazy properties */ + return; + } + + ZEND_ASSERT(Z_TYPE_P(dst) == IS_UNDEF && "Lazy property should be UNDEF"); + + ZVAL_COPY_PROP(dst, src); + + /* Object becomes non-lazy if this was the last lazy prop */ + if (prop_was_lazy && zend_object_is_lazy(object) + && !zend_lazy_object_initialized(object)) { + if (zend_lazy_object_decr_lazy_props(object)) { + zend_lazy_object_realize(object); + } } } diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index 6420b04520aa6..d676597fd0bed 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -47,6 +47,7 @@ extern PHPAPI zend_class_entry *reflection_enum_ptr; extern PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; extern PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; extern PHPAPI zend_class_entry *reflection_fiber_ptr; +extern PHPAPI zend_class_entry *reflection_lazy_object_ptr; PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 2bb9a9a5efa01..366c13f3a1a1d 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -250,6 +250,12 @@ class ReflectionClass implements Reflector /** @cvalue ZEND_ACC_READONLY_CLASS */ public const int IS_READONLY = UNKNOWN; + /** @cvalue ZEND_LAZY_OBJECT_SKIP_INITIALIZATION_ON_SERIALIZE */ + public const int SKIP_INITIALIZATION_ON_SERIALIZE = UNKNOWN; + + /** @cvalue ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR */ + public const int SKIP_DESTRUCTOR = UNKNOWN; + public string $name; private function __clone(): void {} @@ -370,6 +376,22 @@ public function newInstanceWithoutConstructor(): object {} /** @tentative-return-type */ public function newInstanceArgs(array $args = []): ?object {} + public function newLazyGhost(callable $initializer, int $options = 0): object {} + + public function newLazyProxy(callable $factory, int $options = 0): object {} + + public function resetAsLazyGhost(object $object, callable $factory, int $options = 0): void {} + + public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void {} + + public function initializeLazyObject(object $object): object {} + + public function isUninitializedLazyObject(object $object): bool {} + + public function markLazyObjectAsInitialized(object $object): object {} + + public function getLazyInitializer(object $object): ?callable {} + /** @tentative-return-type */ public function getParentClass(): ReflectionClass|false {} @@ -472,6 +494,10 @@ public function getRawValue(object $object): mixed {} public function setRawValue(object $object, mixed $value): void {} + public function setRawValueWithoutLazyInitialization(object $object, mixed $value): void {} + + public function skipLazyInitialization(object $object): void {} + /** @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 19812c409c053..5d95b25582988 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: 28fde6ed0e247201ee25d608d27a4c5b0bb8f2f7 */ + * Stub hash: 09e21577c53d8b53e30aa30e3208d3807ecd8852 */ 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) @@ -287,6 +287,38 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, args, IS_ARRAY, 0, "[]") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_newLazyGhost, 0, 1, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, initializer, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_newLazyProxy, 0, 1, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, factory, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_resetAsLazyGhost, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, factory, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionClass_resetAsLazyProxy arginfo_class_ReflectionClass_resetAsLazyGhost + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_initializeLazyObject, 0, 1, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_isUninitializedLazyObject, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionClass_markLazyObjectAsInitialized arginfo_class_ReflectionClass_initializeLazyObject + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_getLazyInitializer, 0, 1, IS_CALLABLE, 1) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_ReflectionClass_getParentClass, 0, 0, ReflectionClass, MAY_BE_FALSE) ZEND_END_ARG_INFO() @@ -361,6 +393,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_setRawV ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionProperty_setRawValueWithoutLazyInitialization arginfo_class_ReflectionProperty_setRawValue + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_skipLazyInitialization, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + 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() @@ -763,6 +801,14 @@ ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); ZEND_METHOD(ReflectionClass, newInstanceWithoutConstructor); ZEND_METHOD(ReflectionClass, newInstanceArgs); +ZEND_METHOD(ReflectionClass, newLazyGhost); +ZEND_METHOD(ReflectionClass, newLazyProxy); +ZEND_METHOD(ReflectionClass, resetAsLazyGhost); +ZEND_METHOD(ReflectionClass, resetAsLazyProxy); +ZEND_METHOD(ReflectionClass, initializeLazyObject); +ZEND_METHOD(ReflectionClass, isUninitializedLazyObject); +ZEND_METHOD(ReflectionClass, markLazyObjectAsInitialized); +ZEND_METHOD(ReflectionClass, getLazyInitializer); ZEND_METHOD(ReflectionClass, getParentClass); ZEND_METHOD(ReflectionClass, isSubclassOf); ZEND_METHOD(ReflectionClass, getStaticProperties); @@ -785,6 +831,8 @@ ZEND_METHOD(ReflectionProperty, getValue); ZEND_METHOD(ReflectionProperty, setValue); ZEND_METHOD(ReflectionProperty, getRawValue); ZEND_METHOD(ReflectionProperty, setRawValue); +ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization); +ZEND_METHOD(ReflectionProperty, skipLazyInitialization); ZEND_METHOD(ReflectionProperty, isInitialized); ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPrivate); @@ -1044,6 +1092,14 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, newInstance, arginfo_class_ReflectionClass_newInstance, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstanceWithoutConstructor, arginfo_class_ReflectionClass_newInstanceWithoutConstructor, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstanceArgs, arginfo_class_ReflectionClass_newInstanceArgs, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, newLazyGhost, arginfo_class_ReflectionClass_newLazyGhost, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, newLazyProxy, arginfo_class_ReflectionClass_newLazyProxy, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, resetAsLazyGhost, arginfo_class_ReflectionClass_resetAsLazyGhost, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, resetAsLazyProxy, arginfo_class_ReflectionClass_resetAsLazyProxy, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, initializeLazyObject, arginfo_class_ReflectionClass_initializeLazyObject, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isUninitializedLazyObject, arginfo_class_ReflectionClass_isUninitializedLazyObject, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, markLazyObjectAsInitialized, arginfo_class_ReflectionClass_markLazyObjectAsInitialized, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, getLazyInitializer, arginfo_class_ReflectionClass_getLazyInitializer, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getParentClass, arginfo_class_ReflectionClass_getParentClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isSubclassOf, arginfo_class_ReflectionClass_isSubclassOf, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getStaticProperties, arginfo_class_ReflectionClass_getStaticProperties, ZEND_ACC_PUBLIC) @@ -1080,6 +1136,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, setValue, arginfo_class_ReflectionProperty_setValue, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getRawValue, arginfo_class_ReflectionProperty_getRawValue, ZEND_ACC_PUBLIC) 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, 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) @@ -1443,6 +1501,18 @@ static zend_class_entry *register_class_ReflectionClass(zend_class_entry *class_ zend_declare_typed_class_constant(class_entry, const_IS_READONLY_name, &const_IS_READONLY_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_IS_READONLY_name); + zval const_SKIP_INITIALIZATION_ON_SERIALIZE_value; + ZVAL_LONG(&const_SKIP_INITIALIZATION_ON_SERIALIZE_value, ZEND_LAZY_OBJECT_SKIP_INITIALIZATION_ON_SERIALIZE); + zend_string *const_SKIP_INITIALIZATION_ON_SERIALIZE_name = zend_string_init_interned("SKIP_INITIALIZATION_ON_SERIALIZE", sizeof("SKIP_INITIALIZATION_ON_SERIALIZE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_SKIP_INITIALIZATION_ON_SERIALIZE_name, &const_SKIP_INITIALIZATION_ON_SERIALIZE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_SKIP_INITIALIZATION_ON_SERIALIZE_name); + + zval const_SKIP_DESTRUCTOR_value; + ZVAL_LONG(&const_SKIP_DESTRUCTOR_value, ZEND_LAZY_OBJECT_SKIP_DESTRUCTOR); + zend_string *const_SKIP_DESTRUCTOR_name = zend_string_init_interned("SKIP_DESTRUCTOR", sizeof("SKIP_DESTRUCTOR") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_SKIP_DESTRUCTOR_name, &const_SKIP_DESTRUCTOR_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_SKIP_DESTRUCTOR_name); + zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 15059211ef01d..9b5c7db436b09 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -11,11 +11,13 @@ echo $rc; --EXPECT-- Class [ class ReflectionClass implements Stringable, Reflector ] { - - Constants [4] { + - Constants [6] { Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 } Constant [ public int IS_EXPLICIT_ABSTRACT ] { 64 } Constant [ public int IS_FINAL ] { 32 } Constant [ public int IS_READONLY ] { 65536 } + Constant [ public int SKIP_INITIALIZATION_ON_SERIALIZE ] { 8 } + Constant [ public int SKIP_DESTRUCTOR ] { 16 } } - Static properties [0] { @@ -28,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [56] { + - Methods [64] { Method [ private method __clone ] { - Parameters [0] { @@ -330,6 +332,76 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ ?object ] } + Method [ public method newLazyGhost ] { + + - Parameters [2] { + Parameter #0 [ callable $initializer ] + Parameter #1 [ int $options = 0 ] + } + - Return [ object ] + } + + Method [ public method newLazyProxy ] { + + - Parameters [2] { + Parameter #0 [ callable $factory ] + Parameter #1 [ int $options = 0 ] + } + - Return [ object ] + } + + Method [ public method resetAsLazyGhost ] { + + - Parameters [3] { + Parameter #0 [ object $object ] + Parameter #1 [ callable $factory ] + Parameter #2 [ int $options = 0 ] + } + - Return [ void ] + } + + Method [ public method resetAsLazyProxy ] { + + - Parameters [3] { + Parameter #0 [ object $object ] + Parameter #1 [ callable $factory ] + Parameter #2 [ int $options = 0 ] + } + - Return [ void ] + } + + Method [ public method initializeLazyObject ] { + + - Parameters [1] { + Parameter #0 [ object $object ] + } + - Return [ object ] + } + + Method [ public method isUninitializedLazyObject ] { + + - Parameters [1] { + Parameter #0 [ object $object ] + } + - Return [ bool ] + } + + Method [ public method markLazyObjectAsInitialized ] { + + - Parameters [1] { + Parameter #0 [ object $object ] + } + - Return [ object ] + } + + Method [ public method getLazyInitializer ] { + + - Parameters [1] { + Parameter #0 [ object $object ] + } + - Return [ ?callable ] + } + Method [ public method getParentClass ] { - Parameters [0] { diff --git a/ext/standard/var.c b/ext/standard/var.c index 7e51a23e3c029..0d08a0a338309 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -23,11 +23,13 @@ #include "php.h" #include "php_string.h" #include "php_var.h" +#include "zend_lazy_objects.h" #include "zend_smart_str.h" #include "basic_functions.h" #include "php_incomplete_class.h" #include "zend_enum.h" #include "zend_exceptions.h" +#include "zend_types.h" /* }}} */ struct php_serialize_data { @@ -86,6 +88,18 @@ static void php_object_property_dump(zend_property_info *prop_info, zval *zv, ze } /* }}} */ +static const char *php_var_dump_object_prefix(zend_object *obj) { + if (EXPECTED(!zend_object_is_lazy(obj))) { + return ""; + } + + if (zend_object_is_lazy_proxy(obj)) { + return "lazy proxy "; + } + + return "lazy ghost "; +} + PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ { HashTable *myht; @@ -163,7 +177,9 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); - php_printf("%sobject(%s)#%d (%d) {\n", COMMON, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0); + const char *prefix = php_var_dump_object_prefix(Z_OBJ_P(struc)); + + php_printf("%s%sobject(%s)#%d (%d) {\n", COMMON, prefix, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0); zend_string_release_ex(class_name, 0); if (myht) { @@ -360,7 +376,9 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */ myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG); class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc)); - php_printf("object(%s)#%d (%d) refcount(%u){\n", ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0, Z_REFCOUNT_P(struc)); + const char *prefix = php_var_dump_object_prefix(Z_OBJ_P(struc)); + + php_printf("%sobject(%s)#%d (%d) refcount(%u){\n", prefix, ZSTR_VAL(class_name), Z_OBJ_HANDLE_P(struc), myht ? zend_array_count(myht) : 0, Z_REFCOUNT_P(struc)); zend_string_release_ex(class_name, 0); if (myht) { ZEND_HASH_FOREACH_KEY_VAL(myht, index, key, val) { @@ -1206,7 +1224,8 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ if (Z_OBJ_P(struc)->properties == NULL && Z_OBJ_HT_P(struc)->get_properties_for == NULL - && Z_OBJ_HT_P(struc)->get_properties == zend_std_get_properties) { + && Z_OBJ_HT_P(struc)->get_properties == zend_std_get_properties + && !zend_object_is_lazy(Z_OBJ_P(struc))) { /* Optimized version without rebulding properties HashTable */ zend_object *obj = Z_OBJ_P(struc); zend_class_entry *ce = obj->ce; diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 668995045725b..08edb81d11947 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +240,8 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ - zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c"); + zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c \ + zend_lazy_objects.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({