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
This commit is contained in:
Ilija Tovilo 2024-10-16 18:58:42 +02:00
parent cb3bca249a
commit 1f6fdde646
No known key found for this signature in database
GPG key ID: 5050C66BFCD1015A
5 changed files with 224 additions and 7 deletions

View file

@ -122,6 +122,8 @@ PHP 8.5 UPGRADE NOTES
it can be used to suppress warnings emitted by #[\NoDiscard] and possibly
also diagnostics emitted by external IDEs or static analysis tools.
RFC: https://wiki.php.net/rfc/marking_return_value_as_important
. Added asymmetric visibility support for static properties.
RFC: https://wiki.php.net/rfc/static-aviz
- Curl:
. Added support for share handles that are persisted across multiple PHP

View file

@ -5,8 +5,145 @@ Asymmetric visibility on static props
class C {
public private(set) static int $prop;
public private(set) static array $prop2;
public private(set) static stdClass $prop3;
public private(set) static object $unset;
public static function reset() {
self::$prop = 1;
self::$prop2 = [];
self::$prop3 = new stdClass();
}
public static function setProp($prop) {
self::$prop = $prop;
}
public static function addProp2($prop2) {
self::$prop2[] = $prop2;
}
}
function test() {
C::reset();
try {
C::$prop = 2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
C::setProp(3);
var_dump(C::$prop);
try {
++C::$prop;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
try {
C::$prop++;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
try {
C::$prop += str_repeat('a', 10);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
try {
$ref = &C::$prop;
$ref++;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
try {
$ref = 4;
C::$prop = &$ref;
$ref++;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop);
try {
C::$prop2[] = 'foo';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump(C::$prop2);
C::addProp2('bar');
var_dump(C::$prop2);
C::$prop3->foo = 'foo';
var_dump(C::$prop3);
unset(C::$unset->foo);
}
test();
echo "\nRepeat:\n";
test();
?>
--EXPECTF--
Fatal error: Static property may not have asymmetric visibility in %s on line %d
Cannot modify private(set) property C::$prop from global scope
int(1)
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop2 from global scope
array(0) {
}
array(1) {
[0]=>
string(3) "bar"
}
object(stdClass)#%d (1) {
["foo"]=>
string(3) "foo"
}
Repeat:
Cannot modify private(set) property C::$prop from global scope
int(1)
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop from global scope
int(3)
Cannot indirectly modify private(set) property C::$prop2 from global scope
array(0) {
}
array(1) {
[0]=>
string(3) "bar"
}
object(stdClass)#%d (1) {
["foo"]=>
string(3) "foo"
}

View file

@ -8694,10 +8694,6 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f
zend_error_noreturn(E_COMPILE_ERROR, "Property cannot be both final and private");
}
if ((flags & ZEND_ACC_STATIC) && (flags & ZEND_ACC_PPP_SET_MASK)) {
zend_error_noreturn(E_COMPILE_ERROR, "Static property may not have asymmetric visibility");
}
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
if (flags & ZEND_ACC_FINAL) {
zend_error_noreturn(E_COMPILE_ERROR, "Property in interface cannot be final");

View file

@ -1116,6 +1116,14 @@ ZEND_VM_HANDLER(29, ZEND_ASSIGN_STATIC_PROP_OP, ANY, ANY, OP)
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
FREE_OP_DATA();
HANDLE_EXCEPTION();
}
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
do {
@ -1430,6 +1438,13 @@ ZEND_VM_HANDLER(38, ZEND_PRE_INC_STATIC_PROP, ANY, ANY, CACHE_SLOT)
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
zend_pre_incdec_property_zval(prop,
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
@ -1457,6 +1472,13 @@ ZEND_VM_HANDLER(40, ZEND_POST_INC_STATIC_PROP, ANY, ANY, CACHE_SLOT)
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
zend_post_incdec_property_zval(prop,
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
@ -1836,18 +1858,29 @@ ZEND_VM_INLINE_HELPER(zend_fetch_static_prop_helper, ANY, ANY, int type)
{
USE_OPLINE
zval *prop;
zend_property_info *prop_info;
SAVE_OPLINE();
prop = zend_fetch_static_property_address(
NULL, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
&prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC);
if (UNEXPECTED(!prop)) {
ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS));
prop = &EG(uninitialized_zval);
} else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
if (Z_TYPE_P(prop) == IS_OBJECT) {
ZEND_VM_C_GOTO(copy_deref);
} else if (type != BP_VAR_UNSET || Z_TYPE_P(prop) != IS_UNDEF) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
}
prop = &EG(uninitialized_zval);
}
if (type == BP_VAR_R || type == BP_VAR_IS) {
ZEND_VM_C_LABEL(copy_deref):
ZVAL_COPY_DEREF(EX_VAR(opline->result.var), prop);
} else {
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)
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
FREE_OP_DATA();
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
value_ptr = GET_OP_DATA_ZVAL_PTR_PTR(BP_VAR_W);
if (OP_DATA_TYPE == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION) && UNEXPECTED(!Z_ISREF_P(value_ptr))) {

43
Zend/zend_vm_execute.h generated
View file

@ -783,6 +783,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_OP_SPEC_HAN
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
FREE_OP((opline+1)->op1_type, (opline+1)->op1.var);
HANDLE_EXCEPTION();
}
value = get_op_data_zval_ptr_r((opline+1)->op1_type, (opline+1)->op1);
do {
@ -826,6 +834,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_STATIC_PROP_SPEC_HANDL
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
zend_pre_incdec_property_zval(prop,
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
@ -847,6 +862,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_STATIC_PROP_SPEC_HAND
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
zend_post_incdec_property_zval(prop,
ZEND_TYPE_IS_SET(prop_info->type) ? prop_info : NULL OPLINE_CC EXECUTE_DATA_CC);
@ -858,18 +880,29 @@ static zend_always_inline ZEND_OPCODE_HANDLER_RET zend_fetch_static_prop_helper_
{
USE_OPLINE
zval *prop;
zend_property_info *prop_info;
SAVE_OPLINE();
prop = zend_fetch_static_property_address(
NULL, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
&prop_info, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS, type,
type == BP_VAR_W ? opline->extended_value : 0 OPLINE_CC EXECUTE_DATA_CC);
if (UNEXPECTED(!prop)) {
ZEND_ASSERT(EG(exception) || (type == BP_VAR_IS));
prop = &EG(uninitialized_zval);
} else if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
if (Z_TYPE_P(prop) == IS_OBJECT) {
goto copy_deref;
} else if (type != BP_VAR_UNSET || Z_TYPE_P(prop) != IS_UNDEF) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
}
prop = &EG(uninitialized_zval);
}
if (type == BP_VAR_R || type == BP_VAR_IS) {
copy_deref:
ZVAL_COPY_DEREF(EX_VAR(opline->result.var), prop);
} else {
ZVAL_INDIRECT(EX_VAR(opline->result.var), prop);
@ -1105,6 +1138,14 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_REF_SPEC_HA
HANDLE_EXCEPTION();
}
if (UNEXPECTED(prop_info->flags & ZEND_ACC_PPP_SET_MASK)
&& UNEXPECTED(!zend_asymmetric_property_has_set_access(prop_info))) {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
FREE_OP((opline+1)->op1_type, (opline+1)->op1.var);
UNDEF_RESULT();
HANDLE_EXCEPTION();
}
value_ptr = get_zval_ptr_ptr((opline+1)->op1_type, (opline+1)->op1, BP_VAR_W);
if ((opline+1)->op1_type == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION) && UNEXPECTED(!Z_ISREF_P(value_ptr))) {