1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00
This commit is contained in:
Bob Weinand
2025-07-22 17:47:20 +02:00
10 changed files with 241 additions and 5 deletions

View File

@@ -0,0 +1,24 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (protected(set) on non-hooked property)
--FILE--
<?php
class P {
public mixed $foo { get => 42; }
}
class C1 extends P {
public protected(set) mixed $foo = 1;
}
class C2 extends P {
public protected(set) mixed $foo;
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(43)

26
Zend/tests/gh19044.phpt Normal file
View File

@@ -0,0 +1,26 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype
--FILE--
<?php
abstract class P {
protected $foo;
}
class C1 extends P {
protected $foo = 1;
}
class C2 extends P {
protected $foo = 2;
static function foo($c) { return $c->foo; }
}
var_dump(C2::foo(new C2));
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(2)
int(1)

View File

@@ -0,0 +1,26 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (common ancestor has a protected setter)
--FILE--
<?php
abstract class P {
abstract public mixed $foo { get; }
}
class C1 extends P {
public protected(set) mixed $foo { get => 1; set {} }
}
class GrandC1 extends C1 {
public protected(set) mixed $foo { get => 2; set {} }
}
class C2 extends C1 {
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new GrandC1));
?>
--EXPECT--
int(3)

View File

@@ -0,0 +1,26 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (common ancestor does not have a setter)
--FILE--
<?php
abstract class P {
abstract public mixed $foo { get; }
}
class C1 extends P {
public mixed $foo { get => 1; }
}
class GrandC1 extends C1 {
public protected(set) mixed $foo { get => 2; set {} }
}
class C2 extends C1 {
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new GrandC1));
?>
--EXPECT--
int(3)

View File

@@ -0,0 +1,24 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (abstract parent defining visibility only takes precedence)
--FILE--
<?php
abstract class P {
abstract protected(set) mixed $foo { get; set; }
}
class C1 extends P {
public protected(set) mixed $foo { get => 2; set {} }
}
class C2 extends P {
public mixed $foo = 1;
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(3)

View File

@@ -0,0 +1,28 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (abstract parent sets protected(set) with not having grandparent a setter - both inherit from parent)
--FILE--
<?php
abstract class GP {
abstract mixed $foo { get; }
}
abstract class P extends GP {
abstract protected(set) mixed $foo { get; set; }
}
class C1 extends P {
public protected(set) mixed $foo { get => 2; set {} }
}
class C2 extends P {
public mixed $foo = 1;
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(3)

View File

@@ -0,0 +1,28 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (abstract parent sets protected(set) with not having grandparent a setter - one inherits from grandparent)
--FILE--
<?php
abstract class GP {
abstract mixed $foo { get; }
}
abstract class P extends GP {
abstract protected(set) mixed $foo { get; set; }
}
class C1 extends P {
public protected(set) mixed $foo { get => 2; set {} }
}
class C2 extends GP {
public mixed $foo = 1;
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(3)

View File

@@ -0,0 +1,28 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (abstract parent has implicit set hook)
--FILE--
<?php
abstract class GP {
public abstract mixed $foo { get; }
}
class P extends GP {
public protected(set) mixed $foo { get => $this->foo; }
}
class C1 extends P {
public protected(set) mixed $foo = 1;
}
class C2 extends P {
public protected(set) mixed $foo;
static function foo($c) { return $c->foo += 1; }
}
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(2)

View File

@@ -0,0 +1,26 @@
--TEST--
GH-19044: Protected properties must be scoped according to their prototype (hooks variation)
--FILE--
<?php
abstract class P {
abstract protected $foo { get; }
}
class C1 extends P {
protected $foo = 1;
}
class C2 extends P {
protected $foo = 2;
static function foo($c) { return $c->foo; }
}
var_dump(C2::foo(new C2));
var_dump(C2::foo(new C1));
?>
--EXPECT--
int(2)
int(1)

View File

@@ -285,7 +285,7 @@ static zend_always_inline bool is_derived_class(const zend_class_entry *child_cl
static zend_never_inline int is_protected_compatible_scope(const zend_class_entry *ce, const zend_class_entry *scope) /* {{{ */
{
return scope &&
(is_derived_class(ce, scope) || is_derived_class(scope, ce));
(ce == scope || is_derived_class(ce, scope) || is_derived_class(scope, ce));
}
/* }}} */
@@ -422,7 +422,7 @@ wrong:
}
} else {
ZEND_ASSERT(flags & ZEND_ACC_PROTECTED);
if (UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) {
if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) {
goto wrong;
}
}
@@ -517,7 +517,7 @@ wrong:
}
} else {
ZEND_ASSERT(flags & ZEND_ACC_PROTECTED);
if (UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) {
if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) {
goto wrong;
}
}
@@ -588,7 +588,7 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p
return true;
}
return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET)
&& is_protected_compatible_scope(prop_info->ce, scope));
&& is_protected_compatible_scope(prop_info->prototype->ce, scope));
}
static void zend_property_guard_dtor(zval *el) /* {{{ */ {
@@ -2033,7 +2033,7 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend
const zend_class_entry *scope = get_fake_or_executed_scope();
if (property_info->ce != scope) {
if (UNEXPECTED(property_info->flags & ZEND_ACC_PRIVATE)
|| UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) {
|| UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) {
if (type != BP_VAR_IS) {
zend_bad_property_access(property_info, ce, property_name);
}