diff --git a/Zend/tests/lazy_objects/foreach_reset.phpt b/Zend/tests/lazy_objects/foreach_reset.phpt new file mode 100644 index 0000000000000..2c3113c74f173 --- /dev/null +++ b/Zend/tests/lazy_objects/foreach_reset.phpt @@ -0,0 +1,108 @@ +--TEST-- +Lazy Objects: resetAsLazy() during foreach() is not allowed +--FILE-- + $value) { + var_dump($key); + try { + $reflector->resetAsLazyProxy($obj, function () {}); + } catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } + } +} + +$nohooks = new ReflectionClass(NoHooks::class); +$hooks = new ReflectionClass(Hooks::class); + +$obj = $nohooks->newLazyGhost(function () {}); + +test('Ghost', $obj); + +$obj = $hooks->newLazyGhost(function () {}); + +test('Ghost (hooks)', $obj); + +$obj = $nohooks->newLazyProxy(function () { + return new NoHooks(); +}); + +test('Proxy', $obj); + +$obj = $hooks->newLazyProxy(function () { + return new Hooks(); +}); + +test('Proxy', $obj); + +$obj = new NoHooks(); + +test('Non lazy', $obj); + +$obj = new Hooks(); + +test('Non lazy (hooks)', $obj); + +$obj = new NoHooks(); +ob_start(); +var_dump($obj); +ob_end_clean(); + +test('Non lazy with ht', $obj); + +$obj = new Hooks(); +ob_start(); +var_dump($obj); +ob_end_clean(); + +test('Non lazy with ht (hooks)', $obj); + +?> +==DONE== +--EXPECT-- +# Ghost +string(1) "a" +Error: Can not reset an object during property iteration +# Ghost (hooks) +string(1) "a" +Error: Can not reset an object during property iteration +string(1) "b" +Error: Can not reset an object during property iteration +# Proxy +string(1) "a" +Error: Can not reset an object during property iteration +# Proxy +string(1) "a" +Error: Can not reset an object during property iteration +string(1) "b" +Error: Can not reset an object during property iteration +# Non lazy +string(1) "a" +Error: Can not reset an object during property iteration +# Non lazy (hooks) +string(1) "a" +Error: Can not reset an object during property iteration +string(1) "b" +Error: Can not reset an object during property iteration +# Non lazy with ht +string(1) "a" +Error: Can not reset an object during property iteration +# Non lazy with ht (hooks) +string(1) "a" +Error: Can not reset an object during property iteration +string(1) "b" +Error: Can not reset an object during property iteration +==DONE== diff --git a/Zend/tests/lazy_objects/gh15823.phpt b/Zend/tests/lazy_objects/gh15823.phpt new file mode 100644 index 0000000000000..6164e54ea239b --- /dev/null +++ b/Zend/tests/lazy_objects/gh15823.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-15823: Wrong expectations in zend_lazy_object_get_properties() +--FILE-- +newLazyGhost(function ($obj) use (&$calls) { + if ($calls++ === 0) { + throw new Error("initializer"); + } + $obj->a = 2; +}); + +// Builds properties ht without lazy initialization +var_dump($obj); +try { + // Lazy initialization fails during fetching of properties ht + json_encode($obj); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +var_dump(json_encode($obj)); + +?> +--EXPECTF-- +lazy ghost object(C)#%d (0) { + ["a"]=> + uninitialized(int) +} +Error: initializer +string(7) "{"a":2}" diff --git a/Zend/tests/lazy_objects/init_trigger_foreach.phpt b/Zend/tests/lazy_objects/init_trigger_foreach.phpt index b8bd780666bb8..fda50b84c73cd 100644 --- a/Zend/tests/lazy_objects/init_trigger_foreach.phpt +++ b/Zend/tests/lazy_objects/init_trigger_foreach.phpt @@ -5,9 +5,11 @@ Lazy objects: Foreach initializes object class C { public int $a; + public int $b; public function __construct() { var_dump(__METHOD__); $this->a = 1; + $this->b = 2; } } @@ -24,6 +26,17 @@ foreach ($obj as $prop => $value) { var_dump($prop, $value); } +print "# Ghost (by ref):\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) { @@ -35,14 +48,110 @@ foreach ($obj as $prop => $value) { var_dump($prop, $value); } ---EXPECTF-- +print "# Proxy (by ref):\n"; + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +foreach ($obj as $prop => &$value) { + var_dump($prop, $value); +} + +print "# Ghost (init failure)\n"; + +$fail = true; +$obj = $reflector->newLazyGhost(function ($obj) use (&$fail) { + if ($fail) { + throw new Exception("initializer"); + } else { + var_dump("initializer"); + $obj->__construct(); + } +}); + +try { + foreach ($obj as $prop => $value) { + var_dump($prop, $value); + } +} catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +$fail = false; +foreach ($obj as $prop => $value) { + var_dump($prop, $value); +} + +print "# Ghost (init failure, by ref)\n"; + +$fail = true; +$obj = $reflector->newLazyGhost(function ($obj) use (&$fail) { + if ($fail) { + throw new Exception("initializer"); + } else { + var_dump("initializer"); + $obj->__construct(); + } +}); + +try { + foreach ($obj as $prop => &$value) { + var_dump($prop, $value); + } +} catch (Exception $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} + +$fail = false; +foreach ($obj as $prop => &$value) { + var_dump($prop, $value); +} + +?> +--EXPECT-- # Ghost: string(11) "initializer" string(14) "C::__construct" string(1) "a" int(1) +string(1) "b" +int(2) +# Ghost (by ref): +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) # Proxy: string(11) "initializer" string(14) "C::__construct" string(1) "a" int(1) +string(1) "b" +int(2) +# Proxy (by ref): +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +# Ghost (init failure) +Exception: initializer +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +# Ghost (init failure, by ref) +Exception: initializer +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) diff --git a/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt b/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt index d955c92c5ab01..27ac8bf7228dd 100644 --- a/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt +++ b/Zend/tests/lazy_objects/init_trigger_foreach_hooks.phpt @@ -6,49 +6,79 @@ Lazy objects: Foreach initializes object #[AllowDynamicProperties] class C { public int $a; + private int $_b; public int $b { - get { return $this->b; } - set(int $value) { $this->b = $value; } + &get { $ref = &$this->_b; return $ref; } } - public int $c { - get { return $this->a + 2; } - } - public function __construct() { + public function __construct(bool $addDynamic = true) { var_dump(__METHOD__); $this->a = 1; - $this->b = 2; - $this->d = 4; + $this->_b = 2; + if ($addDynamic) { + $this->c = 3; + $this->d = 4; + unset($this->c); + } } } $reflector = new ReflectionClass(C::class); -print "# Ghost:\n"; +function test(string $name, object $obj) { + printf("# %s:\n", $name); + foreach ($obj as $prop => $value) { + var_dump($prop, $value); + } + foreach ($obj as $prop => &$value) { + var_dump($prop, $value); + } +} $obj = $reflector->newLazyGhost(function ($obj) { var_dump("initializer"); $obj->__construct(); }); -foreach ($obj as $prop => $value) { - var_dump($prop, $value); -} +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); -print "# Proxy:\n"; +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(addDynamic: false); +}); + +test('Ghost (no dynamic)', $obj); $obj = $reflector->newLazyProxy(function ($obj) { var_dump("initializer"); + return new C(addDynamic: false); +}); + +test('Proxy (no dynamic)', $obj); + +print "# Proxy of proxy (initialization)\n"; + +$obj = $reflector->newLazyProxy(function ($obj) use (&$obj2, $reflector) { + var_dump("initializer"); + return $obj2 = new C(); +}); +$reflector->initializeLazyObject($obj); +$reflector->resetAsLazyProxy($obj2, function () { return new C(); }); -foreach ($obj as $prop => $value) { - var_dump($prop, $value); -} +test('Proxy of proxy', $obj); print "# Ghost (init exception):\n"; $obj = $reflector->newLazyGhost(function ($obj) { - throw new \Exception(); + throw new \Exception("initializer"); }); try { @@ -60,7 +90,7 @@ try { print "# Proxy (init exception):\n"; $obj = $reflector->newLazyProxy(function ($obj) { - throw new \Exception(); + throw new \Exception("initializer"); }); try { @@ -77,8 +107,12 @@ string(1) "a" int(1) string(1) "b" int(2) -string(1) "c" -int(3) +string(1) "d" +int(4) +string(1) "a" +int(1) +string(1) "b" +int(2) string(1) "d" int(4) # Proxy: @@ -88,9 +122,54 @@ string(1) "a" int(1) string(1) "b" int(2) -string(1) "c" -int(3) +string(1) "d" +int(4) +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "d" +int(4) +# Ghost (no dynamic): +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "a" +int(1) +string(1) "b" +int(2) +# Proxy (no dynamic): +string(11) "initializer" +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "a" +int(1) +string(1) "b" +int(2) +# Proxy of proxy (initialization) +string(11) "initializer" +string(14) "C::__construct" +# Proxy of proxy: +string(14) "C::__construct" +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "d" +int(4) +string(1) "a" +int(1) +string(1) "b" +int(2) +string(1) "d" +int(4) # Ghost (init exception): -Exception: +Exception: initializer # Proxy (init exception): -Exception: +Exception: initializer 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 index d99a30b3c1164..9de565a929a32 100644 --- a/Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt +++ b/Zend/tests/lazy_objects/reset_as_lazy_resets_dynamic_props.phpt @@ -47,7 +47,7 @@ var_dump($obj); --EXPECTF-- # Ghost: string(18) "Canary::__destruct" -lazy ghost object(C)#%d (0) { +lazy ghost object(C)#%d (%d) { } string(11) "initializer" object(Canary)#%d (0) { @@ -62,7 +62,7 @@ object(C)#%d (2) { # Proxy: string(18) "Canary::__destruct" string(18) "Canary::__destruct" -lazy proxy object(C)#%d (0) { +lazy proxy object(C)#%d (%d) { } string(11) "initializer" object(Canary)#%d (0) { diff --git a/Zend/tests/property_hooks/foreach.phpt b/Zend/tests/property_hooks/foreach.phpt index c6073b50ed990..d4b4378a32384 100644 --- a/Zend/tests/property_hooks/foreach.phpt +++ b/Zend/tests/property_hooks/foreach.phpt @@ -23,7 +23,9 @@ class ByRef { } } public function __construct() { + $this->undef = 'dynamic'; $this->dynamic = 'dynamic'; + unset($this->undef); } } diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index 8be8e8b7b773c..6c2204ba29b51 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -199,6 +199,27 @@ ZEND_API bool zend_class_can_be_lazy(zend_class_entry *ce) return true; } +static int zlo_hash_remove_dyn_props_func(zval *pDest) +{ + if (Z_TYPE_P(pDest) == IS_INDIRECT) { + return ZEND_HASH_APPLY_STOP; + } + + return ZEND_HASH_APPLY_REMOVE; +} + +static bool zlo_is_iterating(zend_object *object) +{ + if (object->properties && HT_ITERATORS_COUNT(object->properties)) { + return true; + } + if (zend_object_is_lazy_proxy(object) + && zend_lazy_object_initialized(object)) { + return zlo_is_iterating(zend_lazy_object_get_instance(object)); + } + return false; +} + /* 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, @@ -251,6 +272,10 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj, } } } else { + if (zlo_is_iterating(obj)) { + zend_throw_error(NULL, "Can not reset an object during property iteration"); + return NULL; + } 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); @@ -278,9 +303,17 @@ ZEND_API zend_object *zend_object_make_lazy(zend_object *obj, GC_DEL_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); - /* unset() dynamic properties */ - zend_object_dtor_dynamic_properties(obj); - obj->properties = NULL; + /* unset() dynamic properties. Do not NULL out obj->properties, as this + * would be unexpected. */ + if (obj->properties) { + if (UNEXPECTED(GC_REFCOUNT(obj->properties) > 1)) { + if (EXPECTED(!(GC_FLAGS(obj->properties) & IS_ARRAY_IMMUTABLE))) { + GC_DELREF(obj->properties); + } + obj->properties = zend_array_dup(obj->properties); + } + zend_hash_reverse_apply(obj->properties, zlo_hash_remove_dyn_props_func); + } /* unset() declared properties */ for (int i = 0; i < reflection_ce->default_properties_count; i++) { @@ -513,6 +546,9 @@ ZEND_API zend_object *zend_lazy_object_init(zend_object *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); + if (zend_object_is_lazy(info->u.instance)) { + return zend_lazy_object_init(info->u.instance); + } return info->u.instance; } @@ -624,8 +660,10 @@ ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *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; + if (object->properties) { + return object->properties; + } + return object->properties = zend_new_array(0); } object = tmp; diff --git a/Zend/zend_property_hooks.c b/Zend/zend_property_hooks.c index 523584de943df..398bf2e559e7b 100644 --- a/Zend/zend_property_hooks.c +++ b/Zend/zend_property_hooks.c @@ -51,13 +51,6 @@ 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_new_array(0); - } - } - zend_class_entry *ce = zobj->ce; zend_array *properties = zend_new_array(ce->default_properties_count); zend_hash_real_init_mixed(properties); @@ -93,23 +86,22 @@ static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, ZEND_API zend_array *zend_hooked_object_build_properties(zend_object *zobj) { + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(!zobj)) { + return (zend_array*) &zend_empty_array; + } + } + return zho_build_properties_ex(zobj, false, true); } -static bool zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter) +static void zho_dynamic_it_init(zend_hooked_object_iterator *hooked_iter) { - if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { - return true; - } - zend_object *zobj = Z_OBJ_P(&hooked_iter->it.data); - if (!zobj->properties || zho_num_backed_props(zobj) == zobj->properties->nNumUsed) { - hooked_iter->dynamic_props_done = true; - return false; - } - - hooked_iter->dynamic_prop_it = zend_hash_iterator_add(zobj->properties, zho_num_backed_props(zobj)); - return true; + zend_array *properties = zobj->handlers->get_properties(zobj); + hooked_iter->dynamic_props_done = false; + hooked_iter->dynamic_prop_it = zend_hash_iterator_add(properties, zho_num_backed_props(zobj)); } static void zho_it_get_current_key(zend_object_iterator *iter, zval *key); @@ -163,14 +155,21 @@ static void zho_declared_it_fetch_current(zend_object_iterator *iter) static void zho_dynamic_it_fetch_current(zend_object_iterator *iter) { zend_hooked_object_iterator *hooked_iter = (zend_hooked_object_iterator*)iter; - ZEND_ASSERT(hooked_iter->dynamic_prop_it != (uint32_t) -1); - zend_array *properties = Z_OBJ(iter->data)->properties; HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); + if (pos >= properties->nNumUsed) { + hooked_iter->dynamic_props_done = true; + return; + } + Bucket *bucket = properties->arData + pos; + + if (UNEXPECTED(Z_TYPE(bucket->val) == IS_UNDEF)) { + return; + } + if (hooked_iter->by_ref && Z_TYPE(bucket->val) != IS_REFERENCE) { - ZEND_ASSERT(Z_TYPE(bucket->val) != IS_UNDEF); ZVAL_MAKE_REF(&bucket->val); } ZVAL_COPY(&hooked_iter->current_data, &bucket->val); @@ -192,7 +191,7 @@ static void zho_it_fetch_current(zend_object_iterator *iter) while (true) { if (!hooked_iter->declared_props_done) { zho_declared_it_fetch_current(iter); - } else if (!hooked_iter->dynamic_props_done && zho_dynamic_it_init(hooked_iter)) { + } else if (!hooked_iter->dynamic_props_done) { zho_dynamic_it_fetch_current(iter); } else { break; @@ -211,9 +210,7 @@ static void zho_it_dtor(zend_object_iterator *iter) zval_ptr_dtor(&hooked_iter->declared_props); zval_ptr_dtor_nogc(&hooked_iter->current_key); zval_ptr_dtor(&hooked_iter->current_data); - if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { - zend_hash_iterator_del(hooked_iter->dynamic_prop_it); - } + zend_hash_iterator_del(hooked_iter->dynamic_prop_it); } static zend_result zho_it_valid(zend_object_iterator *iter) @@ -252,14 +249,11 @@ static void zho_it_move_forward(zend_object_iterator *iter) if (zend_hash_has_more_elements(properties) != SUCCESS) { hooked_iter->declared_props_done = true; } - } else if (!hooked_iter->dynamic_props_done && zho_dynamic_it_init(hooked_iter)) { + } else if (!hooked_iter->dynamic_props_done) { zend_array *properties = Z_OBJ(iter->data)->properties; HashPosition pos = zend_hash_iterator_pos(hooked_iter->dynamic_prop_it, properties); pos++; EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = pos; - if (pos >= properties->nNumUsed) { - hooked_iter->dynamic_props_done = true; - } } } @@ -276,9 +270,7 @@ static void zho_it_rewind(zend_object_iterator *iter) zend_array *properties = Z_ARR(hooked_iter->declared_props); zend_hash_internal_pointer_reset(properties); hooked_iter->dynamic_props_done = false; - if (hooked_iter->dynamic_prop_it != (uint32_t) -1) { - EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = zho_num_backed_props(Z_OBJ(iter->data)); - } + EG(ht_iterators)[hooked_iter->dynamic_prop_it].pos = zho_num_backed_props(Z_OBJ(iter->data)); } static HashTable *zho_it_get_gc(zend_object_iterator *iter, zval **table, int *n) @@ -305,17 +297,24 @@ static const zend_object_iterator_funcs zend_hooked_object_it_funcs = { ZEND_API zend_object_iterator *zend_hooked_object_get_iterator(zend_class_entry *ce, zval *object, int by_ref) { + zend_object *zobj = Z_OBJ_P(object); + if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(!zobj)) { + return NULL; + } + } + zend_hooked_object_iterator *iterator = emalloc(sizeof(zend_hooked_object_iterator)); zend_iterator_init(&iterator->it); - ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object)); + ZVAL_OBJ_COPY(&iterator->it.data, zobj); iterator->it.funcs = &zend_hooked_object_it_funcs; iterator->by_ref = by_ref; iterator->declared_props_done = false; - zend_array *properties = zho_build_properties_ex(Z_OBJ_P(object), true, false); + zend_array *properties = zho_build_properties_ex(zobj, true, false); ZVAL_ARR(&iterator->declared_props, properties); - iterator->dynamic_props_done = false; - iterator->dynamic_prop_it = (uint32_t) -1; + zho_dynamic_it_init(iterator); ZVAL_UNDEF(&iterator->current_key); ZVAL_UNDEF(&iterator->current_data); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 55675e4a0b5c2..7263c6eef6dee 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6821,6 +6821,14 @@ ZEND_VM_HANDLER(77, ZEND_FE_RESET_R, CONST|TMP|VAR|CV, JMP_ADDR) } else if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(array_ptr); if (!zobj->ce->get_iterator) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + FREE_OP1_IF_VAR(); + HANDLE_EXCEPTION(); + } + } HashTable *properties = zobj->properties; if (properties) { if (UNEXPECTED(GC_REFCOUNT(properties) > 1)) { @@ -6909,7 +6917,16 @@ ZEND_VM_COLD_CONST_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, JMP_ADDR) ZEND_VM_NEXT_OPCODE(); } else if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + FREE_OP1_IF_VAR(); + HANDLE_EXCEPTION(); + } + } if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) { if (array_ptr == array_ref) { ZVAL_NEW_REF(array_ref, array_ref); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 088c29cf408e8..f0ecc76bbbc99 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5411,6 +5411,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_R_SPEC_CONST_HANDLER( } else if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(array_ptr); if (!zobj->ce->get_iterator) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } HashTable *properties = zobj->properties; if (properties) { if (UNEXPECTED(GC_REFCOUNT(properties) > 1)) { @@ -5497,7 +5505,16 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_ ZEND_VM_NEXT_OPCODE(); } else if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } if (IS_CONST == IS_VAR || IS_CONST == IS_CV) { if (array_ptr == array_ref) { ZVAL_NEW_REF(array_ref, array_ref); @@ -20117,6 +20134,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_R_SPEC_TMP_HANDLER(ZE } else if (IS_TMP_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(array_ptr); if (!zobj->ce->get_iterator) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } HashTable *properties = zobj->properties; if (properties) { if (UNEXPECTED(GC_REFCOUNT(properties) > 1)) { @@ -20204,7 +20229,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_TMP_HANDLER(Z ZEND_VM_NEXT_OPCODE(); } else if (IS_TMP_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } if (IS_TMP_VAR == IS_VAR || IS_TMP_VAR == IS_CV) { if (array_ptr == array_ref) { ZVAL_NEW_REF(array_ref, array_ref); @@ -22769,6 +22803,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_R_SPEC_VAR_HANDLER(ZE } else if (IS_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(array_ptr); if (!zobj->ce->get_iterator) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + HANDLE_EXCEPTION(); + } + } HashTable *properties = zobj->properties; if (properties) { if (UNEXPECTED(GC_REFCOUNT(properties) > 1)) { @@ -22857,7 +22899,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_VAR_HANDLER(Z ZEND_VM_NEXT_OPCODE(); } else if (IS_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + HANDLE_EXCEPTION(); + } + } if (IS_VAR == IS_VAR || IS_VAR == IS_CV) { if (array_ptr == array_ref) { ZVAL_NEW_REF(array_ref, array_ref); @@ -41067,6 +41118,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_R_SPEC_CV_HANDLER(ZEN } else if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { zend_object *zobj = Z_OBJ_P(array_ptr); if (!zobj->ce->get_iterator) { + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } HashTable *properties = zobj->properties; if (properties) { if (UNEXPECTED(GC_REFCOUNT(properties) > 1)) { @@ -41153,7 +41212,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_CV_HANDLER(ZE ZEND_VM_NEXT_OPCODE(); } else if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; + if (UNEXPECTED(zend_object_is_lazy(zobj))) { + zobj = zend_lazy_object_init(zobj); + if (UNEXPECTED(EG(exception))) { + UNDEF_RESULT(); + + HANDLE_EXCEPTION(); + } + } if (IS_CV == IS_VAR || IS_CV == IS_CV) { if (array_ptr == array_ref) { ZVAL_NEW_REF(array_ref, array_ref);