Skip to content
31 changes: 31 additions & 0 deletions Zend/tests/lazy_objects/gh17998.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
GH-17998: Skipped lazy init on primed SIMPLE_WRITE
--FILE--
<?php

class C {
public $prop {
set => $value;
}
}

$nonLazy = new C;

$lazy = (new ReflectionClass(C::class))->newLazyProxy(function () {
echo "init\n";
return new C;
});

function foo(C $c) {
$c->prop = 1;
var_dump($c->prop);
}

foo($nonLazy);
foo($lazy);

?>
--EXPECT--
int(1)
init
int(1)
40 changes: 40 additions & 0 deletions Zend/tests/property_hooks/gh17988.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
GH-17988: Incorrect handling of hooked properties without get hook in get_object_vars()
--FILE--
<?php

class C
{
public string $prop {
set => $value;
}
}

$c = new C;
$c->prop = 42;

var_dump($c);
var_dump(get_object_vars($c));
var_export($c);
echo "\n";
var_dump(json_encode($c));
var_dump((array)$c);

?>
--EXPECTF--
object(C)#%d (1) {
["prop"]=>
string(2) "42"
}
array(1) {
["prop"]=>
string(2) "42"
}
\C::__set_state(array(
'prop' => '42',
))
string(13) "{"prop":"42"}"
array(1) {
["prop"]=>
string(2) "42"
}
33 changes: 33 additions & 0 deletions Zend/tests/property_hooks/gh18000.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
GH-18000: Lazy proxy calls set hook twice
--FILE--
<?php

class C {
public $prop {
set {
echo "set\n";
$this->prop = $value * 2;
}
}
}

$rc = new ReflectionClass(C::class);

$obj = $rc->newLazyProxy(function () {
echo "init\n";
return new C;
});

function foo(C $c) {
$c->prop = 1;
var_dump($c->prop);
}

foo($obj);

?>
--EXPECT--
set
init
int(2)
3 changes: 1 addition & 2 deletions Zend/zend_builtin_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -840,15 +840,14 @@ ZEND_FUNCTION(get_object_vars)
}
const char *unmangled_name_cstr = zend_get_unmangled_property_name(prop_info->name);
zend_string *unmangled_name = zend_string_init(unmangled_name_cstr, strlen(unmangled_name_cstr), false);
zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp);
value = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp);
zend_string_release_ex(unmangled_name, false);
if (EG(exception)) {
zend_release_properties(properties);
zval_ptr_dtor(return_value);
ZVAL_UNDEF(return_value);
RETURN_THROWS();
}
value = &tmp;
}
Z_TRY_ADDREF_P(value);

Expand Down
20 changes: 17 additions & 3 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -673,9 +673,23 @@ static bool zend_is_in_hook(const zend_property_info *prop_info)

static bool zend_should_call_hook(const zend_property_info *prop_info, const zend_object *obj)
{
return !zend_is_in_hook(prop_info)
/* execute_data and This are guaranteed to be set if zend_is_in_hook() returns true. */
|| Z_OBJ(EG(current_execute_data)->This) != obj;
if (!zend_is_in_hook(prop_info)) {
return true;
}

/* execute_data and This are guaranteed to be set if zend_is_in_hook() returns true. */
zend_object *parent_obj = Z_OBJ(EG(current_execute_data)->This);
if (parent_obj == obj) {
return false;
}

if (zend_object_is_lazy_proxy(parent_obj)
&& zend_lazy_object_initialized(parent_obj)
&& zend_lazy_object_get_instance(parent_obj) == obj) {
return false;
}

return true;
}

static ZEND_COLD void zend_throw_no_prop_backing_value_access(zend_string *class_name, zend_string *prop_name, bool is_read)
Expand Down
18 changes: 9 additions & 9 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2457,12 +2457,14 @@ ZEND_VM_C_LABEL(assign_object):
void **cache_slot = CACHE_ADDR(opline->extended_value);
uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1);
zval *property_val;
zend_property_info *prop_info;

if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) {
prop_info = (zend_property_info*) CACHED_PTR_EX(cache_slot + 2);

ZEND_VM_C_LABEL(assign_obj_simple):
property_val = OBJ_PROP(zobj, prop_offset);
if (Z_TYPE_P(property_val) != IS_UNDEF) {
zend_property_info *prop_info = (zend_property_info*) CACHED_PTR_EX(cache_slot + 2);

if (prop_info != NULL) {
value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC);
ZEND_VM_C_GOTO(free_and_exit_assign_obj);
Expand Down Expand Up @@ -2534,14 +2536,12 @@ ZEND_VM_C_LABEL(fast_assign_obj):
} else {
ZEND_ASSERT(IS_HOOKED_PROPERTY_OFFSET(prop_offset));
if (ZEND_IS_PROPERTY_HOOK_SIMPLE_WRITE(prop_offset)) {
zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2);
property_val = OBJ_PROP(zobj, prop_info->offset);
if (ZEND_TYPE_IS_SET(prop_info->type)) {
value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC);
ZEND_VM_C_GOTO(free_and_exit_assign_obj);
} else {
ZEND_VM_C_GOTO(fast_assign_obj);
prop_info = CACHED_PTR_EX(cache_slot + 2);
prop_offset = prop_info->offset;
if (!ZEND_TYPE_IS_SET(prop_info->type)) {
prop_info = NULL;
}
ZEND_VM_C_GOTO(assign_obj_simple);
}
/* Fall through to write_property for hooks. */
}
Expand Down
Loading
Loading