Skip to content

Fix GH-19044: Protected properties are not scoped according to their prototype #19046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Adjust after review
  • Loading branch information
bwoebi committed Jul 22, 2025
commit a963439e32a54be3697f157208a921b0eb34c3c4
28 changes: 28 additions & 0 deletions Zend/tests/asymmetric_visibility/gh19044.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--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));

?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot modify protected(set) property C1::$foo from scope C2 in %s:%d
Stack trace:
#0 %s(%d): C2::foo(Object(C1))
#1 {main}
thrown in %s on line %d
28 changes: 28 additions & 0 deletions Zend/tests/property_hooks/gh19044-6.phpt
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 16 additions & 5 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,25 @@ static zend_never_inline int is_protected_compatible_scope(const zend_class_entr
}
/* }}} */

static zend_never_inline int is_asymmetric_set_protected_property_compatible_scope(const zend_property_info *info, const zend_class_entry *scope) /* {{{ */
static int is_asymmetric_set_protected_property_compatible_scope(const zend_property_info *info, const zend_class_entry *scope) /* {{{ */
{
zend_class_entry *ce;
if (!(info->prototype->flags & ZEND_ACC_PROTECTED_SET) && info->hooks && info->hooks[ZEND_PROPERTY_HOOK_SET]) {
zend_function *hookfn = info->hooks[ZEND_PROPERTY_HOOK_SET];
ce = hookfn->common.prototype ? hookfn->common.prototype->common.scope : hookfn->common.scope;
/* we need to identify the common protected(set) ancestor: if the prototype has the protected(set), it's straightforward */
if (info->prototype->flags & ZEND_ACC_PROTECTED_SET) {
ce = info->prototype->ce;
} else if (info->hooks && info->hooks[ZEND_PROPERTY_HOOK_SET]) {
/* shortcut: the visibility of hooks cannot be overwritten */
zend_function *hook = info->hooks[ZEND_PROPERTY_HOOK_SET];
ce = zend_get_function_root_class(hook);
} else {
ce = info->prototype->ce;
/* we do not have an easy way to find the ancestor which introduces the protected(set), let's iterate */
do {
ce = info->ce;
if (!ce->parent->properties_info_table) {
break;
}
info = ce->parent->properties_info_table[OBJ_PROP_TO_NUM(info->offset)];
} while (info->flags & ZEND_ACC_PROTECTED_SET);
}
return is_protected_compatible_scope(ce, scope);
}
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy