mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Merge branch 'PHP-8.1'
* PHP-8.1: Disallow assigning reference to unset readonly property
This commit is contained in:
commit
40908b10fc
8 changed files with 248 additions and 13 deletions
|
@ -19,7 +19,11 @@ function init() {
|
|||
var_dump($c->a);
|
||||
}
|
||||
|
||||
(new C)->init();
|
||||
try {
|
||||
(new C)->init();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
init();
|
||||
|
@ -29,8 +33,5 @@ try {
|
|||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(1) {
|
||||
[0]=>
|
||||
int(1)
|
||||
}
|
||||
Cannot initialize readonly property C::$a from global scope
|
||||
Cannot indirectly modify readonly property C::$a
|
||||
Cannot indirectly modify readonly property C::$a
|
||||
|
|
22
Zend/tests/readonly_props/gh7942.phpt
Normal file
22
Zend/tests/readonly_props/gh7942.phpt
Normal file
|
@ -0,0 +1,22 @@
|
|||
--TEST--
|
||||
GH-7942: Disallow assigning reference to unset readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
public readonly int $bar;
|
||||
public function __construct(int &$bar) {
|
||||
$this->bar = &$bar;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$i = 42;
|
||||
new Foo($i);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot indirectly modify readonly property Foo::$bar
|
118
Zend/tests/readonly_props/variation.phpt
Normal file
118
Zend/tests/readonly_props/variation.phpt
Normal file
|
@ -0,0 +1,118 @@
|
|||
--TEST--
|
||||
Readonly variations
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly int $prop;
|
||||
|
||||
public function init() {
|
||||
$this->prop = 1;
|
||||
}
|
||||
|
||||
public function r() {
|
||||
echo $this->prop;
|
||||
}
|
||||
|
||||
public function w() {
|
||||
$this->prop = 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
public function rw() {
|
||||
$this->prop += 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
public function im() {
|
||||
$this->prop[] = 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
public function is() {
|
||||
echo (int) isset($this->prop);
|
||||
}
|
||||
|
||||
public function us() {
|
||||
unset($this->prop);
|
||||
echo 'done';
|
||||
}
|
||||
}
|
||||
|
||||
function r($test) {
|
||||
echo $test->prop;
|
||||
}
|
||||
|
||||
function w($test) {
|
||||
$test->prop = 0;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function rw($test) {
|
||||
$test->prop += 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function im($test) {
|
||||
$test->prop[] = 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function is($test) {
|
||||
echo (int) isset($test->prop);
|
||||
}
|
||||
|
||||
function us($test) {
|
||||
unset($test->prop);
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
foreach ([true, false] as $init) {
|
||||
foreach ([true, false] as $scope) {
|
||||
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) {
|
||||
$test = new Test();
|
||||
if ($init) {
|
||||
$test->init();
|
||||
}
|
||||
|
||||
echo 'Init: ' . ((int) $init) . ', scope: ' . ((int) $scope) . ', op: ' . $op . ": ";
|
||||
try {
|
||||
if ($scope) {
|
||||
$test->{$op}();
|
||||
} else {
|
||||
$op($test);
|
||||
}
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Init: 1, scope: 1, op: r: 1
|
||||
Init: 1, scope: 1, op: w: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 1, op: rw: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 1, op: im: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 1, op: is: 1
|
||||
Init: 1, scope: 1, op: us: Cannot unset readonly property Test::$prop
|
||||
Init: 1, scope: 0, op: r: 1
|
||||
Init: 1, scope: 0, op: w: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 0, op: rw: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 0, op: im: Cannot modify readonly property Test::$prop
|
||||
Init: 1, scope: 0, op: is: 1
|
||||
Init: 1, scope: 0, op: us: Cannot unset readonly property Test::$prop
|
||||
Init: 0, scope: 1, op: r: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, scope: 1, op: w: done
|
||||
Init: 0, scope: 1, op: rw: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, scope: 1, op: im: Cannot indirectly modify readonly property Test::$prop
|
||||
Init: 0, scope: 1, op: is: 0
|
||||
Init: 0, scope: 1, op: us: done
|
||||
Init: 0, scope: 0, op: r: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, scope: 0, op: w: Cannot initialize readonly property Test::$prop from global scope
|
||||
Init: 0, scope: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, scope: 0, op: im: Cannot indirectly modify readonly property Test::$prop
|
||||
Init: 0, scope: 0, op: is: 0
|
||||
Init: 0, scope: 0, op: us: Cannot unset readonly property Test::$prop from global scope
|
77
Zend/tests/readonly_props/variation_nested.phpt
Normal file
77
Zend/tests/readonly_props/variation_nested.phpt
Normal file
|
@ -0,0 +1,77 @@
|
|||
--TEST--
|
||||
Readonly nested variations
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Inner {
|
||||
public int $prop = 1;
|
||||
public array $array = [];
|
||||
}
|
||||
|
||||
class Test {
|
||||
public readonly Inner $prop;
|
||||
|
||||
public function init() {
|
||||
$this->prop = new Inner();
|
||||
}
|
||||
}
|
||||
|
||||
function r($test) {
|
||||
echo $test->prop->prop;
|
||||
}
|
||||
|
||||
function w($test) {
|
||||
$test->prop->prop = 0;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function rw($test) {
|
||||
$test->prop->prop += 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function im($test) {
|
||||
$test->prop->array[] = 1;
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
function is($test) {
|
||||
echo (int) isset($test->prop->prop);
|
||||
}
|
||||
|
||||
function us($test) {
|
||||
unset($test->prop->prop);
|
||||
echo 'done';
|
||||
}
|
||||
|
||||
foreach ([true, false] as $init) {
|
||||
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) {
|
||||
$test = new Test();
|
||||
if ($init) {
|
||||
$test->init();
|
||||
}
|
||||
|
||||
echo 'Init: ' . ((int) $init) . ', op: ' . $op . ": ";
|
||||
try {
|
||||
$op($test);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Init: 1, op: r: 1
|
||||
Init: 1, op: w: done
|
||||
Init: 1, op: rw: done
|
||||
Init: 1, op: im: done
|
||||
Init: 1, op: is: 1
|
||||
Init: 1, op: us: done
|
||||
Init: 0, op: r: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, op: w: Cannot indirectly modify readonly property Test::$prop
|
||||
Init: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
|
||||
Init: 0, op: im: Cannot indirectly modify readonly property Test::$prop
|
||||
Init: 0, op: is: 0
|
||||
Init: 0, op: us: done
|
|
@ -853,6 +853,12 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(
|
|||
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
|
||||
}
|
||||
|
||||
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info)
|
||||
{
|
||||
zend_throw_error(NULL, "Cannot indirectly modify readonly property %s::$%s",
|
||||
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
|
||||
}
|
||||
|
||||
static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) {
|
||||
if (zend_string_equals_literal_ci(name, "self")) {
|
||||
return self_ce;
|
||||
|
|
|
@ -74,6 +74,7 @@ ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_index_write(HashTable *ht,
|
|||
ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void);
|
||||
|
||||
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(zend_property_info *info);
|
||||
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info);
|
||||
|
||||
ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg);
|
||||
ZEND_API ZEND_COLD void zend_verify_arg_error(
|
||||
|
|
|
@ -622,6 +622,17 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
|
|||
}
|
||||
}
|
||||
goto exit;
|
||||
} else {
|
||||
if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
if (type == BP_VAR_W || type == BP_VAR_RW) {
|
||||
zend_readonly_property_indirect_modification_error(prop_info);
|
||||
retval = &EG(uninitialized_zval);
|
||||
goto exit;
|
||||
} else if (type == BP_VAR_UNSET) {
|
||||
retval = &EG(uninitialized_zval);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (UNEXPECTED(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT)) {
|
||||
/* Skip __get() for uninitialized typed properties */
|
||||
|
@ -1051,9 +1062,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
|
|||
ZVAL_NULL(retval);
|
||||
zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name));
|
||||
}
|
||||
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)
|
||||
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize")) {
|
||||
retval = &EG(error_zval);
|
||||
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
/* Readonly property, delegate to read_property + write_property. */
|
||||
retval = NULL;
|
||||
}
|
||||
} else {
|
||||
/* we do have getter - fail and let it try again with usual get/set */
|
||||
|
|
|
@ -21,11 +21,10 @@ $appendProp2 = (function() {
|
|||
$this->prop[] = 1;
|
||||
})->bindTo($test, Test::class);
|
||||
$appendProp2();
|
||||
$appendProp2();
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Error: Cannot modify readonly property Test::$prop in %sfetch_obj_006.php:13
|
||||
Fatal error: Uncaught Error: Cannot indirectly modify readonly property Test::$prop in %s:%d
|
||||
Stack trace:
|
||||
#0 %sfetch_obj_006.php(16): Test->{closure}()
|
||||
#0 %sfetch_obj_006.php(15): Test->{closure}()
|
||||
#1 {main}
|
||||
thrown in %sfetch_obj_006.php on line 13
|
Loading…
Add table
Add a link
Reference in a new issue