Skip to content

Commit 1f6fdde

Browse files
committed
Implement asymmetric visibility for static properties
https://wiki.php.net/rfc/static-aviz Optimally, this would be moved to zend_fetch_static_property_address(). However, this isn't currently effective for opcache, because R and RW/W/UNSET cache slots are merged. This will circumvent the visibility check if the cache is primed by a R instruction. Closes GH-16486
1 parent cb3bca2 commit 1f6fdde

File tree

5 files changed

+224
-7
lines changed

5 files changed

+224
-7
lines changed

UPGRADING

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ PHP 8.5 UPGRADE NOTES
122122
it can be used to suppress warnings emitted by #[\NoDiscard] and possibly
123123
also diagnostics emitted by external IDEs or static analysis tools.
124124
RFC: https://wiki.php.net/rfc/marking_return_value_as_important
125+
. Added asymmetric visibility support for static properties.
126+
RFC: https://wiki.php.net/rfc/static-aviz
125127

126128
- Curl:
127129
. Added support for share handles that are persisted across multiple PHP

Zend/tests/asymmetric_visibility/static_props.phpt

+138-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,145 @@ Asymmetric visibility on static props
55

66
class C {
77
public private(set) static int $prop;
8+
public private(set) static array $prop2;
9+
public private(set) static stdClass $prop3;
10+
public private(set) static object $unset;
11+
12+
public static function reset() {
13+
self::$prop = 1;
14+
self::$prop2 = [];
15+
self::$prop3 = new stdClass();
16+
}
17+
18+
public static function setProp($prop) {
19+
self::$prop = $prop;
20+
}
21+
22+
public static function addProp2($prop2) {
23+
self::$prop2[] = $prop2;
24+
}
25+
}
26+
27+
function test() {
28+
C::reset();
29+
30+
try {
31+
C::$prop = 2;
32+
} catch (Error $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
var_dump(C::$prop);
36+
37+
C::setProp(3);
38+
var_dump(C::$prop);
39+
40+
try {
41+
++C::$prop;
42+
} catch (Error $e) {
43+
echo $e->getMessage(), "\n";
44+
}
45+
var_dump(C::$prop);
46+
47+
try {
48+
C::$prop++;
49+
} catch (Error $e) {
50+
echo $e->getMessage(), "\n";
51+
}
52+
var_dump(C::$prop);
53+
54+
try {
55+
C::$prop += str_repeat('a', 10);
56+
} catch (Error $e) {
57+
echo $e->getMessage(), "\n";
58+
}
59+
var_dump(C::$prop);
60+
61+
try {
62+
$ref = &C::$prop;
63+
$ref++;
64+
} catch (Error $e) {
65+
echo $e->getMessage(), "\n";
66+
}
67+
var_dump(C::$prop);
68+
69+
try {
70+
$ref = 4;
71+
C::$prop = &$ref;
72+
$ref++;
73+
} catch (Error $e) {
74+
echo $e->getMessage(), "\n";
75+
}
76+
var_dump(C::$prop);
77+
78+
try {
79+
C::$prop2[] = 'foo';
80+
} catch (Error $e) {
81+
echo $e->getMessage(), "\n";
82+
}
83+
var_dump(C::$prop2);
84+
85+
C::addProp2('bar');
86+
var_dump(C::$prop2);
87+
88+
C::$prop3->foo = 'foo';
89+
var_dump(C::$prop3);
90+
91+
unset(C::$unset->foo);
892
}
993

94+
test();
95+
echo "\nRepeat:\n";
96+
test();
97+
1098
?>
1199
--EXPECTF--
12-
Fatal error: Static property may not have asymmetric visibility in %s on line %d
100+
Cannot modify private(set) property C::$prop from global scope
101+
int(1)
102+
int(3)
103+
Cannot indirectly modify private(set) property C::$prop from global scope
104+
int(3)
105+
Cannot indirectly modify private(set) property C::$prop from global scope
106+
int(3)
107+
Cannot indirectly modify private(set) property C::$prop from global scope
108+
int(3)
109+
Cannot indirectly modify private(set) property C::$prop from global scope
110+
int(3)
111+
Cannot indirectly modify private(set) property C::$prop from global scope
112+
int(3)
113+
Cannot indirectly modify private(set) property C::$prop2 from global scope
114+
array(0) {
115+
}
116+
array(1) {
117+
[0]=>
118+
string(3) "bar"
119+
}
120+
object(stdClass)#%d (1) {
121+
["foo"]=>
122+
string(3) "foo"
123+
}
124+
125+
Repeat:
126+
Cannot modify private(set) property C::$prop from global scope
127+
int(1)
128+
int(3)
129+
Cannot indirectly modify private(set) property C::$prop from global scope
130+
int(3)
131+
Cannot indirectly modify private(set) property C::$prop from global scope
132+
int(3)
133+
Cannot indirectly modify private(set) property C::$prop from global scope
134+
int(3)
135+
Cannot indirectly modify private(set) property C::$prop from global scope
136+
int(3)
137+
Cannot indirectly modify private(set) property C::$prop from global scope
138+
int(3)
139+
Cannot indirectly modify private(set) property C::$prop2 from global scope
140+
array(0) {
141+
}
142+
array(1) {
143+
[0]=>
144+
string(3) "bar"
145+
}
146+
object(stdClass)#%d (1) {
147+
["foo"]=>
148+
string(3) "foo"
149+
}

Zend/zend_compile.c

-4
Original file line numberDiff line numberDiff line change
@@ -8694,10 +8694,6 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f
86948694
zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private");
86958695
}
86968696

8697-
if ((flags & ZEND_ACC_STATIC) && (flags & ZEND_ACC_PPP_SET_MASK)) {
8698-
zend_error_noreturn(E_COMPILE_ERROR, "Static property may not have asymmetric visibility");
8699-
}
8700-
87018697
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
87028698
if (flags & ZEND_ACC_FINAL) {
87038699
zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be final");

Zend/zend_vm_def.h

+42-1
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,14 @@ ZEND_VM_HANDLER(29, ZEND_ASSIGN_STATIC_PROP_OP, ANY, ANY, OP)
11161116
HANDLE_EXCEPTION();
11171117
}
11181118

1119+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
1120+
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
1121+
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
1122+
UNDEF_RESULT();
1123+
FREE_OP_DATA();
1124+
HANDLE_EXCEPTION();
1125+
}
1126+
11191127
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
11201128

11211129
do {
@@ -1430,6 +1438,13 @@ ZEND_VM_HANDLER(38, ZEND_PRE_INC_STATIC_PROP, ANY, ANY, CACHE_SLOT)
14301438
HANDLE_EXCEPTION();
14311439
}
14321440

1441+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
1442+
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
1443+
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
1444+
UNDEF_RESULT();
1445+
HANDLE_EXCEPTION();
1446+
}
1447+
14331448
zend_pre_incdec_property_zval(prop,
14341449
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
14351450

@@ -1457,6 +1472,13 @@ ZEND_VM_HANDLER(40, ZEND_POST_INC_STATIC_PROP, ANY, ANY, CACHE_SLOT)
14571472
HANDLE_EXCEPTION();
14581473
}
14591474

1475+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
1476+
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
1477+
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
1478+
UNDEF_RESULT();
1479+
HANDLE_EXCEPTION();
1480+
}
1481+
14601482
zend_post_incdec_property_zval(prop,
14611483
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
14621484

@@ -1836,18 +1858,29 @@ ZEND_VM_INLINE_HELPER(zend_fetch_static_prop_helper, ANY, ANY, int type)
18361858
{
18371859
USE_OPLINE
18381860
zval *prop;
1861+
zend_property_info *prop_info;
18391862

18401863
SAVE_OPLINE();
18411864

18421865
prop = zend_fetch_static_property_address(
1843-
NULL, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
1866+
&prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
18441867
type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC);
18451868
if (UNEXPECTED(!prop)) {
18461869
ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS));
18471870
prop = &EG(uninitialized_zval);
1871+
} else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
1872+
&& (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)
1873+
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
1874+
if (Z_TYPE_P(prop) == IS_OBJECT) {
1875+
ZEND_VM_C_GOTO(copy_deref);
1876+
} else if (type != BP_VAR_UNSET || Z_TYPE_P(prop) != IS_UNDEF) {
1877+
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
1878+
}
1879+
prop = &EG(uninitialized_zval);
18481880
}
18491881

18501882
if (type == BP_VAR_R || type == BP_VAR_IS) {
1883+
ZEND_VM_C_LABEL(copy_deref):
18511884
ZVAL_COPY_DEREF(EX_VAR(opline->result.var), prop);
18521885
} else {
18531886
ZVAL_INDIRECT(EX_VAR(opline->result.var), prop);
@@ -2893,6 +2926,14 @@ ZEND_VM_HANDLER(33, ZEND_ASSIGN_STATIC_PROP_REF, ANY, ANY, CACHE_SLOT|SRC)
28932926
HANDLE_EXCEPTION();
28942927
}
28952928

2929+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
2930+
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
2931+
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
2932+
FREE_OP_DATA();
2933+
UNDEF_RESULT();
2934+
HANDLE_EXCEPTION();
2935+
}
2936+
28962937
value_ptr = GET_OP_DATA_ZVAL_PTR_PTR(BP_VAR_W);
28972938

28982939
if (OP_DATA_TYPE == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION) && UNEXPECTED(!Z_ISREF_P(value_ptr))) {

Zend/zend_vm_execute.h

+42-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)