Commit 9ec303edde5 for php.net

commit 9ec303edde5b05f6287aea3f6eb3ebf477f78d95
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Mar 16 20:20:52 2026 -0400

    Fix GH-20875: Propagate IN_GET guard in get_property_ptr_ptr for lazy proxies

    zend_std_get_property_ptr_ptr() was the only property handler that did
    not propagate the IN_GET guard to the underlying object when forwarding
    from a lazy proxy after initialization. This caused __get to be called
    on the underlying object when it shouldn't be, leading to assertion
    failures.

    The same guard-copying pattern already existed in read_property,
    write_property, unset_property, and has_property since commit
    26f5009e91 (GH-18039).

    Also fixes GH-20873 and GH-20854.

    Closes GH-20875

diff --git a/NEWS b/NEWS
index 8d592bf603f..a5605382424 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,10 @@ PHP                                                                        NEWS
 - Bz2:
   . Fix truncation of total output size causing erroneous errors. (ndossche)

+- Core:
+  . Fixed bugs GH-20875, GH-20873, GH-20854 (Propagate IN_GET guard in
+    get_property_ptr_ptr for lazy proxies). (iliaal)
+
 - GD:
   . Fixed bug GH-21431 (phpinfo() to display libJPEG 10.0 support).
     (David Carlier)
@@ -27,7 +31,7 @@ PHP                                                                        NEWS
 - PGSQL:
   . Fixed preprocessor silently guarding PGSQL_SUPPRESS_TIMESTAMPS support
     due to a typo. (KentarouTakeda)
-
+
 - SNMP:
   . Fixed bug GH-21336 (SNMP::setSecurity() undefined behavior with
     NULL arguments). (David Carlier)
diff --git a/Zend/tests/lazy_objects/gh20854.phpt b/Zend/tests/lazy_objects/gh20854.phpt
new file mode 100644
index 00000000000..26a69cfcbbf
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20854.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-20854 (Assertion in ZEND_RETURN_BY_REF with lazy proxy and return-by-ref __get)
+--FILE--
+<?php
+class C {
+    public $prop;
+
+    function &__get($name) {
+        return $this->x;
+    }
+}
+
+$rc = new ReflectionClass(C::class);
+$obj = $rc->newLazyProxy(function () {
+    return new C;
+});
+$obj->x;
+echo "Done\n";
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property C::$x is deprecated in %s on line %d
+Done
diff --git a/Zend/tests/lazy_objects/gh20873.phpt b/Zend/tests/lazy_objects/gh20873.phpt
new file mode 100644
index 00000000000..e5b8c31e913
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20873.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-20873 (Assertion failure in _zendi_try_convert_scalar_to_number with lazy proxy)
+--FILE--
+<?php
+class A {
+    public $_;
+    public function __get($n) {
+        global $obj;
+        $obj->x =& $this->_;
+        static $a = $a;
+        $e =& $this->_ - $a;
+    }
+}
+$rc = new ReflectionClass(A::class);
+$obj = $rc->newLazyProxy(fn() => new A);
+$rc->initializeLazyObject($obj);
+var_dump($obj->p);
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property A::$x is deprecated in %s on line %d
+
+Warning: Undefined variable $a in %s on line %d
+
+Notice: Indirect modification of overloaded property A::$x has no effect in %s on line %d
+
+Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
+Stack trace:
+#0 %s(%d): A->__get('p')
+#1 {main}
+  thrown in %s on line %d
diff --git a/Zend/tests/lazy_objects/gh20875.phpt b/Zend/tests/lazy_objects/gh20875.phpt
new file mode 100644
index 00000000000..72e16011320
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20875.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GH-20875 (Assertion failure in _get_zval_ptr_tmp with lazy proxy)
+--FILE--
+<?php
+class A {
+    public $_;
+    public function __get($name) {
+        global $obj;
+        $obj->f =& $this->b - $x > $y = new StdClass;
+        static $a = $a;
+        $t = 'x';
+        foreach (get_defined_vars() as $key => $e) {}
+        if ($v ==!1) $x = $a ?: $t = "ok";
+    }
+}
+$rc = new ReflectionClass(A::class);
+$obj = $rc->newLazyProxy(function () { return new A; });
+$real = $rc->initializeLazyObject($obj);
+var_dump($real->prop);
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property A::$b is deprecated in %s on line %d
+
+Deprecated: Creation of dynamic property A::$f is deprecated in %s on line %d
+
+Warning: Undefined variable $x in %s on line %d
+
+Notice: Object of class stdClass could not be converted to int in %s on line %d
+
+Warning: Undefined variable $a in %s on line %d
+
+Warning: Undefined variable $v in %s on line %d
+
+Notice: Indirect modification of overloaded property A::$b has no effect in %s on line %d
+
+Warning: Undefined variable $x in %s on line %d
+
+Notice: Object of class stdClass could not be converted to int in %s on line %d
+
+Warning: Undefined variable $v in %s on line %d
+
+Notice: Indirect modification of overloaded property A::$f has no effect in %s on line %d
+
+Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
+Stack trace:
+#0 %s(%d): A->__get('b')
+#1 %s(%d): A->__get('prop')
+#2 {main}
+  thrown in %s on line %d
diff --git a/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt b/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt
new file mode 100644
index 00000000000..d5d9754864b
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-20875 (Lazy proxy should not initialize when __get handles dynamic property access)
+--FILE--
+<?php
+
+class Foo {
+    public $_;
+
+    public function &__get($name) {
+        echo "__get\n";
+        return $name;
+    }
+}
+
+$proxy = (new ReflectionClass(Foo::class))->newLazyProxy(function () {
+    echo "init\n";
+    return new Foo();
+});
+$x = &$proxy->x;
+
+?>
+--EXPECT--
+__get
diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c
index ef5fb29751b..648e57dfe7e 100644
--- a/Zend/zend_object_handlers.c
+++ b/Zend/zend_object_handlers.c
@@ -1401,12 +1401,24 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
 			    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) {
+					bool guarded = zobj->ce->__get
+						&& (*zend_get_property_guard(zobj, name) & IN_GET);
+					zend_object *instance = zend_lazy_object_init(zobj);
+					if (!instance) {
 						return &EG(error_zval);
 					}

-					return zend_std_get_property_ptr_ptr(zobj, name, type, cache_slot);
+					if (guarded && (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
+						uint32_t *guard = zend_get_property_guard(instance, name);
+						if (!(*guard & IN_GET)) {
+							(*guard) |= IN_GET;
+							retval = zend_std_get_property_ptr_ptr(instance, name, type, cache_slot);
+							(*guard) &= ~IN_GET;
+							return retval;
+						}
+					}
+
+					return zend_std_get_property_ptr_ptr(instance, name, type, cache_slot);
 				}
 				if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) {
 					if (prop_info) {
@@ -1449,6 +1461,25 @@ 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)) {
+			if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
+				bool guarded = (zobj->ce->__get != NULL);
+				zend_object *instance = zend_lazy_object_init(zobj);
+				if (!instance) {
+					return &EG(error_zval);
+				}
+
+				if (guarded && (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
+					uint32_t *guard = zend_get_property_guard(instance, name);
+					if (!(*guard & IN_GET)) {
+						(*guard) |= IN_GET;
+						retval = zend_std_get_property_ptr_ptr(instance, name, type, cache_slot);
+						(*guard) &= ~IN_GET;
+						return retval;
+					}
+				}
+
+				return zend_std_get_property_ptr_ptr(instance, name, type, cache_slot);
+			}
 			if (UNEXPECTED(zobj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
 				zend_forbidden_dynamic_property(zobj->ce, name);
 				return &EG(error_zval);
@@ -1458,14 +1489,6 @@ 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);
 			}