Skip to content

Commit d1c7272

Browse files
committed
handle type visibility
1 parent 017ee02 commit d1c7272

8 files changed

+165
-42
lines changed

Zend/zend_execute.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,12 +1045,57 @@ static zend_always_inline bool i_zend_check_property_type(const zend_property_in
10451045
return zend_verify_scalar_type_hint(type_mask, property, strict, 0);
10461046
}
10471047

1048+
static zend_always_inline bool zend_check_class_visibility(const zend_class_entry *ce, const zend_property_info *info, uint32_t current_visibility) {
1049+
// a public class is always visible
1050+
if (!ce->required_scope) {
1051+
return 1;
1052+
}
1053+
1054+
// a protected class is visible if it is a subclass of the lexical scope and the current visibility is protected or private
1055+
if (!ce->required_scope_absolute) {
1056+
if (current_visibility & ZEND_ACC_PUBLIC) {
1057+
zend_type_error("Cannot assign private %s to higher visibile property %s::%s",
1058+
ZSTR_VAL(ce->name),
1059+
ZSTR_VAL(info->ce->name),
1060+
zend_get_unmangled_property_name(info->name));
1061+
return 0;
1062+
}
1063+
1064+
return 0;
1065+
}
1066+
1067+
// a private class is visible if it is the same class as the lexical scope and the current visibility is private
1068+
if (ce->required_scope_absolute && current_visibility & ZEND_ACC_PRIVATE) {
1069+
return 1;
1070+
}
1071+
1072+
zend_type_error("Cannot assign private %s to higher visibile property %s::%s",
1073+
ZSTR_VAL(ce->name),
1074+
ZSTR_VAL(info->ce->name),
1075+
zend_get_unmangled_property_name(info->name));
1076+
1077+
return 0;
1078+
}
1079+
10481080
static zend_always_inline bool i_zend_verify_property_type(const zend_property_info *info, zval *property, bool strict)
10491081
{
1082+
if(Z_TYPE_P(property) == IS_OBJECT && !zend_check_class_visibility(Z_OBJCE_P(property), info, info->flags)) {
1083+
zend_verify_property_type_error(info, property);
1084+
return 0;
1085+
}
1086+
10501087
if (i_zend_check_property_type(info, property, strict)) {
10511088
return 1;
10521089
}
10531090

1091+
// todo:
1092+
// 1: add a flag to the type so we can tell the type is an inner class
1093+
// 2: use said flag to flag the property info
1094+
// 3: same with parameters/args too
1095+
// 4: create a simple function to take a visibility flag and ce, it should return SUCCESS if the ce can be used
1096+
// 5: if we have an inner class in a prop/arg, we validate it can be returned (do not autoload) by looping over types
1097+
// that are inner classes and looking up the ce. If it is not autoloaded, then it is not going to match the type anyway.
1098+
10541099
zend_verify_property_type_error(info, property);
10551100
return 0;
10561101
}

Zend/zend_object_handlers.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c
401401
if (property_info->ce != ce) {
402402
goto dynamic;
403403
} else {
404+
if (scope && scope->lexical_scope && scope->lexical_scope == ce) {
405+
// Allow access to private properties from within the same outer class
406+
goto found;
407+
}
404408
wrong:
405409
/* Information was available, but we were denied access. Error out. */
406410
if (!silent) {

Zend/zend_vm_def.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,12 +1869,22 @@ ZEND_VM_HANDLER(210, ZEND_FETCH_INNER_CLASS, CONST|TMPVAR|UNUSED, CONST, CACHE_S
18691869
}
18701870

18711871
if (inner_ce->required_scope) {
1872-
if (inner_ce->required_scope_absolute && inner_ce->required_scope != scope) {
1873-
zend_error(E_ERROR, "Class '%s' is private", ZSTR_VAL(full_class_name));
1874-
HANDLE_EXCEPTION();
1875-
} else if (scope == NULL || !instanceof_function(scope, inner_ce->required_scope)) {
1876-
zend_error(E_ERROR, "Class '%s' is protected", ZSTR_VAL(full_class_name));
1877-
HANDLE_EXCEPTION();
1872+
if (inner_ce->required_scope_absolute) {
1873+
// for private classes, we check if the scope we are currently in has access
1874+
if (scope != NULL && (inner_ce->required_scope == scope || scope->lexical_scope == inner_ce->required_scope)) {
1875+
// we are in the correct scope
1876+
} else {
1877+
zend_error(E_ERROR, "Class '%s' is private", ZSTR_VAL(full_class_name));
1878+
HANDLE_EXCEPTION();
1879+
}
1880+
} else {
1881+
// for protected classes, we check if the scope is an instance of the required scope
1882+
if (scope != NULL && (instanceof_function(scope, inner_ce->required_scope) || instanceof_function(scope->lexical_scope, inner_ce->required_scope))) {
1883+
// we are in the correct scope
1884+
} else {
1885+
zend_error(E_ERROR, "Class '%s' is protected", ZSTR_VAL(full_class_name));
1886+
HANDLE_EXCEPTION();
1887+
}
18781888
}
18791889
}
18801890

tests/classes/inner_classes/properties_001.phpt

Lines changed: 0 additions & 23 deletions
This file was deleted.

tests/classes/inner_classes/visibility_001.phpt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ outer class visibility
44
<?php
55

66
class Outer {
7-
private class Inner {}
8-
private class Other {
9-
public function __construct(private Outer:>Inner $inner) {}
7+
private class Inner {}
8+
public Outer:>Inner $illegal;
109

11-
public function reset(): void {
12-
$prev = $this->inner;
13-
$this->inner = new Outer:>Inner();
14-
var_dump($prev === $this->inner);
10+
public function test(): void {
11+
$this->illegal = new Outer:>Inner();
1512
}
16-
}
17-
public static function test(): void {
18-
new self:>Other(new Outer:>Inner());
19-
}
2013
}
21-
Outer::test();
14+
15+
$x = new Outer();
16+
$x->test();
17+
18+
var_dump($x);
2219
?>
23-
--EXPECT--
20+
--EXPECTF--
21+
Fatal error: Uncaught TypeError: Cannot assign private Outer:>Inner to higher visibile property Outer::illegal in %s:%d
22+
Stack trace:
23+
#0 %s(%d): Outer->test()
24+
#1 {main}
25+
thrown in %s on line %d
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
accessing outer class private vars
3+
--FILE--
4+
<?php
5+
6+
class Outer {
7+
private class Inner {
8+
public function test(Outer $i) {
9+
$i->illegal = $this;
10+
}
11+
}
12+
private Outer:>Inner $illegal;
13+
14+
public function test(): void {
15+
new Outer:>Inner()->test($this);
16+
}
17+
}
18+
19+
$x = new Outer();
20+
$x->test();
21+
22+
var_dump($x);
23+
24+
?>
25+
--EXPECT--
26+
object(Outer)#1 (1) {
27+
["illegal":"Outer":private]=>
28+
object(Outer:>Inner)#2 (0) {
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
accessing outer protected vars
3+
--FILE--
4+
<?php
5+
6+
class Outer {
7+
private class Inner {
8+
public function test(Outer $i) {
9+
$i->illegal = $this;
10+
}
11+
}
12+
private Outer:>Inner $illegal;
13+
14+
public function test(): void {
15+
new Outer:>Inner()->test($this);
16+
}
17+
}
18+
19+
$x = new Outer();
20+
$x->test();
21+
22+
var_dump($x);
23+
24+
?>
25+
--EXPECT--
26+
object(Outer)#1 (1) {
27+
["illegal":"Outer":private]=>
28+
object(Outer:>Inner)#2 (0) {
29+
}
30+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
outer class visibility
3+
--FILE--
4+
<?php
5+
6+
class Outer {
7+
protected class Inner {}
8+
public Outer:>Inner $illegal;
9+
10+
public function test(): void {
11+
$this->illegal = new Outer:>Inner();
12+
}
13+
}
14+
15+
$x = new Outer();
16+
$x->test();
17+
18+
var_dump($x);
19+
?>
20+
--EXPECTF--
21+
Fatal error: Uncaught TypeError: Cannot assign private Outer:>Inner to higher visibile property Outer::illegal in %s:%d
22+
Stack trace:
23+
#0 %s(%d): Outer->test()
24+
#1 {main}
25+
thrown in %s on line %d

0 commit comments

Comments
 (0)