diff --git a/Zend/tests/asymmetric_visibility/static_props.phpt b/Zend/tests/asymmetric_visibility/static_props.phpt index 65fd3aa1923d2..f47dcf70b6f74 100644 --- a/Zend/tests/asymmetric_visibility/static_props.phpt +++ b/Zend/tests/asymmetric_visibility/static_props.phpt @@ -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" +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e33990c73bb67..6f169d2a87715 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8654,10 +8654,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"); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 6c87b81bc3bd7..837a7103de9d5 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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))) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 06b4ad6b1494a..84209b1d9dca0 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -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))) {