Merge branch 'PHP-8.1'

* PHP-8.1:
  Disallow assigning reference to  unset readonly property
This commit is contained in:
Ilija Tovilo 2022-07-01 12:20:32 +02:00
commit 40908b10fc
No known key found for this signature in database
GPG key ID: A4F5D403F118200A
8 changed files with 248 additions and 13 deletions

View file

@ -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

View 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

View 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

View 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

View file

@ -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;

View file

@ -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(

View file

@ -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 */

View file

@ -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