[RFC] Asymmetric visibility v2 (GH-15063)

Co-authored-by: Larry Garfield <larry@garfieldtech.com>
This commit is contained in:
Ilija Tovilo 2024-08-27 02:04:48 +02:00 committed by GitHub
parent fef55bc8e4
commit 8df557ac42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 1768 additions and 155 deletions

1
NEWS
View file

@ -16,6 +16,7 @@ PHP NEWS
undefined). (Peter Kokot) undefined). (Peter Kokot)
. Fixed bug GH-15565 (--disable-ipv6 during compilation produces error . Fixed bug GH-15565 (--disable-ipv6 during compilation produces error
EAI_SYSTEM not found). (nielsdos) EAI_SYSTEM not found). (nielsdos)
. Implemented asymmetric visibility for properties. (ilutov)
- Date: - Date:
. Fixed bug GH-13773 (DatePeriod not taking into account microseconds for end . Fixed bug GH-13773 (DatePeriod not taking into account microseconds for end

View file

@ -0,0 +1,43 @@
--TEST--
Asymmetric visibility __set
--FILE--
<?php
class Foo {
public private(set) string $bar;
public function setBar($bar) {
$this->bar = $bar;
}
public function unsetBar() {
unset($this->bar);
}
public function __set(string $name, mixed $value) {
echo __CLASS__, '::', __METHOD__, "\n";
}
}
$foo = new Foo();
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBar('baz');
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->unsetBar();
$foo->bar = 'baz';
?>
--EXPECT--
Cannot modify private(set) property Foo::$bar from global scope
Cannot modify private(set) property Foo::$bar from global scope
Foo::Foo::__set

View file

@ -0,0 +1,47 @@
--TEST--
Asymmetric visibility __unset
--FILE--
<?php
class Foo {
public private(set) string $bar;
public function setBar($bar) {
$this->bar = $bar;
}
public function unsetBar() {
unset($this->bar);
}
public function __unset($name) {
echo __METHOD__, "\n";
}
}
function test($foo) {
try {
unset($foo->bar);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
$foo = new Foo();
test($foo);
$foo->unsetBar();
test($foo);
$foo->setBar('bar');
test($foo);
$foo->unsetBar();
test($foo);
?>
--EXPECT--
Cannot unset private(set) property Foo::$bar from global scope
Foo::__unset
Cannot unset private(set) property Foo::$bar from global scope
Foo::__unset

View file

@ -0,0 +1,28 @@
--TEST--
private(set) protected(set) ast printing
--INI--
zend.assertions=1
assert.exception=1
--FILE--
<?php
try {
assert(function () {
class Foo {
public private(set) string $bar;
public protected(set) string $baz;
}
} && false);
} catch (Error $e) {
echo $e->getMessage();
}
?>
--EXPECT--
assert(function () {
class Foo {
public private(set) string $bar;
public protected(set) string $baz;
}
} && false)

View file

@ -0,0 +1,33 @@
--TEST--
Unset from __unset respects set visibility
--FILE--
<?php
class C {
public private(set) int $a = 1;
public function __construct() {
unset($this->a);
}
}
class D extends C {
public function __unset($name) {
unset($this->a);
}
}
$c = new D();
try {
unset($c->a);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($c);
?>
--EXPECTF--
Cannot unset private(set) property C::$a from scope D
object(D)#%d (0) {
["a"]=>
uninitialized(int)
}

View file

@ -0,0 +1,33 @@
--TEST--
Set from __set respects set visibility
--FILE--
<?php
class C {
public private(set) int $a = 1;
public function __construct() {
unset($this->a);
}
}
class D extends C {
public function __set($name, $value) {
$this->a = $value;
}
}
$c = new D();
try {
$c->a = 2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($c);
?>
--EXPECTF--
Cannot modify private(set) property C::$a from scope D
object(D)#%d (0) {
["a"]=>
uninitialized(int)
}

View file

@ -0,0 +1,28 @@
--TEST--
Explicitly unset property with a-vis still respects set visibility
--FILE--
<?php
class C {
public private(set) int $a = 1;
public function __construct() {
unset($this->a);
}
}
$c = new C();
try {
$c->a = 2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
unset($c->a);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Cannot modify private(set) property C::$a from global scope
Cannot unset private(set) property C::$a from global scope

View file

@ -0,0 +1,18 @@
--TEST--
Missing property initialization for private(set) constructor promoted property
--FILE--
<?php
class T {
public function __construct(
private(set) string $prop,
) {}
}
var_dump(new T('Test'));
?>
--EXPECTF--
object(T)#%d (1) {
["prop"]=>
string(4) "Test"
}

View file

@ -0,0 +1,14 @@
--TEST--
Asymmetric visibility in CPP with no type
--FILE--
<?php
class Foo {
public function __construct(
public private(set) $bar,
) {}
}
?>
--EXPECTF--
Fatal error: Property with asymmetric visibility Foo::$bar must have type in %s on line %d

View file

@ -0,0 +1,32 @@
--TEST--
Asymmetric visibility private(set) CPP
--FILE--
<?php
class Foo {
public function __construct(
public private(set) string $bar,
) {}
public function setBar($bar) {
$this->bar = $bar;
}
}
$foo = new Foo('bar');
var_dump($foo->bar);
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBar('baz');
var_dump($foo->bar);
?>
--EXPECT--
string(3) "bar"
Cannot modify private(set) property Foo::$bar from global scope
string(3) "baz"

View file

@ -0,0 +1,42 @@
--TEST--
Asymmetric visibility protected(set) CPP
--FILE--
<?php
class Foo {
public function __construct(
public protected(set) string $bar,
) {}
public function setBarPrivate($bar) {
$this->bar = $bar;
}
}
class FooChild extends Foo {
public function setBarProtected($bar) {
$this->bar = $bar;
}
}
$foo = new FooChild('bar');
var_dump($foo->bar);
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBarPrivate('baz');
var_dump($foo->bar);
$foo->setBarProtected('qux');
var_dump($foo->bar);
?>
--EXPECT--
string(3) "bar"
Cannot modify protected(set) property Foo::$bar from global scope
string(3) "baz"
string(3) "qux"

View file

@ -0,0 +1,14 @@
--TEST--
Asymmetric visibility private(get) protected(set) in CPP is not allowed
--FILE--
<?php
class Foo {
public function __construct(
private protected(set) string $bar
) {}
}
?>
--EXPECTF--
Fatal error: Visibility of property Foo::$bar must not be weaker than set visibility in %s on line %d

View file

@ -0,0 +1,13 @@
--TEST--
Asymmetric visibility private(get) private(set) is allowed
--FILE--
<?php
class Foo {
private private(set) string $bar;
}
?>
===DONE===
--EXPECT--
===DONE===

View file

@ -0,0 +1,12 @@
--TEST--
Asymmetric visibility private(get) protected(set) not allowed
--FILE--
<?php
class Foo {
private protected(set) string $bar;
}
?>
--EXPECTF--
Fatal error: Visibility of property Foo::$bar must not be weaker than set visibility in %s on line %d

View file

@ -0,0 +1,13 @@
--TEST--
Asymmetric visibility protected(get) protected(set) is allowed
--FILE--
<?php
class Foo {
protected protected(set) string $bar;
}
?>
===DONE===
--EXPECT--
===DONE===

View file

@ -0,0 +1,34 @@
--TEST--
Asymmetric visibility DIM add
--FILE--
<?php
class Foo {
public private(set) array $bars = [];
public function addBar($bar) {
$this->bars[] = $bar;
}
}
$foo = new Foo();
try {
$foo->bars[] = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($foo->bars);
$foo->addBar('baz');
var_dump($foo->bars);
?>
--EXPECT--
Cannot indirectly modify private(set) property Foo::$bars from global scope
array(0) {
}
array(1) {
[0]=>
string(3) "baz"
}

View file

@ -0,0 +1,12 @@
--TEST--
Duplicate asymmetric visibility modifier
--FILE--
<?php
class Foo {
private(set) protected(set) string $bar;
}
?>
--EXPECTF--
Fatal error: Multiple access type modifiers are not allowed in %s on line %d

View file

@ -0,0 +1,12 @@
--TEST--
Duplicate asymmetric visibility modifier
--FILE--
<?php
class Foo {
public(set) protected(set) string $bar;
}
?>
--EXPECTF--
Fatal error: Multiple access type modifiers are not allowed in %s on line %d

View file

@ -0,0 +1,24 @@
--TEST--
Asymmetric visibility nested write
--FILE--
<?php
class Foo {
public private(set) Bar $bar;
public function __construct() {
$this->bar = new Bar;
}
}
class Bar {
public int $baz;
}
$foo = new Foo();
$foo->bar->baz = 42;
var_dump($foo->bar->baz);
?>
--EXPECT--
int(42)

View file

@ -0,0 +1,12 @@
--TEST--
Asymmetric visibility with no type
--FILE--
<?php
class Foo {
public private(set) $bar;
}
?>
--EXPECTF--
Fatal error: Property with asymmetric visibility Foo::$bar must have type in %s on line %d

View file

@ -0,0 +1,37 @@
--TEST--
Attempting to obtain reference of object of private(set) object returns a copy instead
--FILE--
<?php
class Foo {
public private(set) Bar $bar;
public function __construct() {
$this->bar = new Bar();
}
}
class Bar {}
function test() {
$foo = new Foo();
$bar = &$foo->bar;
var_dump($foo);
}
test();
// Test zend_fetch_property_address with warmed cache
test();
?>
--EXPECT--
object(Foo)#1 (1) {
["bar"]=>
object(Bar)#2 (0) {
}
}
object(Foo)#2 (1) {
["bar"]=>
object(Bar)#1 (0) {
}
}

View file

@ -0,0 +1,16 @@
--TEST--
private(set) property is implicitly final
--FILE--
<?php
class A {
public private(set) string $foo;
}
class B extends A {
public string $foo;
}
?>
--EXPECTF--
Fatal error: Cannot override final property A::$foo in %s on line %d

View file

@ -0,0 +1,16 @@
--TEST--
Overwritten protected asymmetric property with private asymmetric property
--FILE--
<?php
class A {
public protected(set) string $foo;
}
class B extends A {
public private(set) string $foo;
}
?>
--EXPECTF--
Fatal error: Set access level of B::$foo must be protected(set) (as in class A) or weaker in %s on line %d

View file

@ -0,0 +1,20 @@
--TEST--
Overwritten protected asymmetric property with public property
--FILE--
<?php
class A {
public protected(set) string $foo;
}
class B extends A {
public string $foo;
}
$b = new B();
$b->foo = 'foo';
echo $b->foo, "\n";
?>
--EXPECT--
foo

View file

@ -0,0 +1,16 @@
--TEST--
Overwritten public property with private asymmetric property
--FILE--
<?php
class A {
public string $foo;
}
class B extends A {
public private(set) string $foo;
}
?>
--EXPECTF--
Fatal error: Set access level of B::$foo must be omitted (as in class A) in %s on line %d

View file

@ -0,0 +1,16 @@
--TEST--
Overwritten public property with protected asymmetric property
--FILE--
<?php
class A {
public string $foo;
}
class B extends A {
public protected(set) string $foo;
}
?>
--EXPECTF--
Fatal error: Set access level of B::$foo must be omitted (as in class A) in %s on line %d

View file

@ -0,0 +1,61 @@
--TEST--
Asymmetric visibility private(set)
--FILE--
<?php
class Foo {
public private(set) string $bar = 'bar';
private(set) string $baz = 'baz';
public function setBar($bar) {
$this->bar = $bar;
}
public function setBaz($baz) {
$this->baz = $baz;
}
}
class FooChild extends Foo {
public function modifyBar($bar) {
$this->bar = $bar;
}
}
$foo = new Foo();
var_dump($foo->bar);
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBar('baz');
var_dump($foo->bar);
try {
$foo->baz = 'baz2';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBaz('baz2');
var_dump($foo->baz);
$child = new FooChild();
try {
$child->modifyBar('baz');
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
string(3) "bar"
Cannot modify private(set) property Foo::$bar from global scope
string(3) "baz"
Cannot modify private(set) property Foo::$baz from global scope
string(4) "baz2"
Cannot modify private(set) property Foo::$bar from scope FooChild

View file

@ -0,0 +1,66 @@
--TEST--
Asymmetric visibility protected(set)
--FILE--
<?php
class Foo {
public protected(set) string $bar = 'bar';
protected(set) string $baz = 'baz';
public function setBarPrivate($bar) {
$this->bar = $bar;
}
public function setBazPrivate($baz) {
$this->baz = $baz;
}
}
class FooChild extends Foo {
public function setBarProtected($bar) {
$this->bar = $bar;
}
public function setBazProtected($baz) {
$this->baz = $baz;
}
}
$foo = new FooChild();
var_dump($foo->bar);
try {
$foo->bar = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setBarPrivate('baz');
var_dump($foo->bar);
$foo->setBarProtected('qux');
var_dump($foo->bar);
try {
$foo->baz = 'baz';
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$foo->setbazPrivate('baz2');
var_dump($foo->baz);
$foo->setbazProtected('baz3');
var_dump($foo->baz);
?>
--EXPECT--
string(3) "bar"
Cannot modify protected(set) property Foo::$bar from global scope
string(3) "baz"
string(3) "qux"
Cannot modify protected(set) property Foo::$baz from global scope
string(4) "baz2"
string(4) "baz3"

View file

@ -0,0 +1,62 @@
--TEST--
Asymmetric visibility with readonly
--FILE--
<?php
class P {
public readonly int $pDefault;
public private(set) readonly int $pPrivate;
public protected(set) readonly int $pProtected;
public public(set) readonly int $pPublic;
public function __construct() {
$this->pDefault = 1;
$this->pPrivate = 1;
$this->pProtected = 1;
$this->pPublic = 1;
}
}
class C extends P {
public function __construct() {
$this->pDefault = 1;
try {
$this->pPrivate = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$this->pProtected = 1;
$this->pPublic = 1;
}
}
function test() {
$p = new ReflectionClass(P::class)->newInstanceWithoutConstructor();
try {
$p->pDefault = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$p->pPrivate = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$p->pProtected = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$p->pPublic = 1;
}
new P();
new C();
test();
?>
--EXPECT--
Cannot modify private(set) property P::$pPrivate from scope C
Cannot modify protected(set) property P::$pDefault from global scope
Cannot modify private(set) property P::$pPrivate from global scope
Cannot modify protected(set) property P::$pProtected from global scope

View file

@ -0,0 +1,32 @@
--TEST--
Asymmetric visibility reference
--FILE--
<?php
class Foo {
public private(set) int $bar = 0;
public function test() {
$bar = &$this->bar;
$bar++;
}
}
$foo = new Foo();
try {
$bar = &$foo->bar;
$bar++;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($foo->bar);
$foo->test();
var_dump($foo->bar);
?>
--EXPECT--
Cannot indirectly modify private(set) property Foo::$bar from global scope
int(0)
int(1)

View file

@ -0,0 +1,30 @@
--TEST--
Asymmetric visibility reference in forbidden scope
--FILE--
<?php
class C {
public private(set) int $prop = 1;
}
function test($c) {
$prop = &$c->prop;
$prop = 42;
}
try {
test(new C());
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
test(new C());
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Cannot indirectly modify private(set) property C::$prop from global scope
Cannot indirectly modify private(set) property C::$prop from global scope

View file

@ -0,0 +1,36 @@
--TEST--
Changing scope with Closure::bindTo() does not confuse asymmetric visibility
--FILE--
<?php
class Foo {
public private(set) int $bar = 1;
}
class Bar {}
$foo = new Foo();
$c = function () use ($foo) {
$foo->bar++;
};
($c->bindTo(null, Foo::class))();
var_dump($foo->bar);
try {
$c();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
($c->bindTo(null, Bar::class))();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($foo->bar);
?>
--EXPECT--
int(2)
Cannot modify private(set) property Foo::$bar from global scope
Cannot modify private(set) property Foo::$bar from scope Bar
int(2)

View file

@ -0,0 +1,67 @@
--TEST--
Asymmetric visibility unset protected(set)
--FILE--
<?php
class Foo {
public protected(set) string $bar;
public private(set) string $secret;
public function setBar($bar) {
$this->bar = $bar;
}
public function setSecret($s) {
$this->secret = $s;
}
public function unsetBarPrivate() {
unset($this->bar);
}
}
class FooChild extends Foo {
public function unsetBarProtected() {
unset($this->bar);
}
public function unsetSecret() {
unset($this->secret);
}
}
$foo = new FooChild();
$foo->setBar('bar');
try {
unset($foo->bar);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($foo->bar ?? 'unset');
$foo->setBar('bar');
$foo->unsetBarPrivate();
var_dump($foo->bar ?? 'unset');
$foo->setBar('bar');
$foo->unsetBarProtected();
var_dump($foo->bar ?? 'unset');
$foo->setSecret('beep');
try {
$foo->unsetSecret();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($foo->secret ?? 'unset');
?>
--EXPECT--
Cannot unset protected(set) property Foo::$bar from global scope
string(3) "bar"
string(5) "unset"
string(5) "unset"
Cannot unset private(set) property Foo::$secret from scope FooChild
string(4) "beep"

View file

@ -0,0 +1,37 @@
--TEST--
R/w cache slots should be unshared
--FILE--
<?php
class P {
public private(set) string $bar;
}
class C extends P {
public function setBar($bar) {
var_dump($this->bar);
$this->bar = $bar;
}
}
$c = new C();
try {
$c->setBar(1);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$c->setBar(1);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($c);
?>
--EXPECTF--
Typed property P::$bar must not be accessed before initialization
Typed property P::$bar must not be accessed before initialization
object(C)#%d (0) {
["bar"]=>
uninitialized(string)
}

View file

@ -0,0 +1,134 @@
--TEST--
Asymmetric visibility variations
--FILE--
<?php
class Test {
public private(set) int $prop;
public private(set) array $array;
public function init() {
$this->prop = 1;
$this->array = [];
}
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->array[] = 1;
echo 'done';
}
public function is() {
echo (int) isset($this->prop);
}
public function us() {
unset($this->prop);
echo 'done';
}
public function us_dim() {
unset($this->array[0]);
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->array[] = 1;
echo 'done';
}
function is($test) {
echo (int) isset($test->prop);
}
function us($test) {
unset($test->prop);
echo 'done';
}
function us_dim($test) {
unset($test->array[0]);
echo 'done';
}
foreach ([true, false] as $init) {
foreach ([true, false] as $scope) {
foreach (['r', 'w', 'rw', 'im', 'is', 'us', 'us_dim'] 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: done
Init: 1, scope: 1, op: rw: done
Init: 1, scope: 1, op: im: done
Init: 1, scope: 1, op: is: 1
Init: 1, scope: 1, op: us: done
Init: 1, scope: 1, op: us_dim: done
Init: 1, scope: 0, op: r: 1
Init: 1, scope: 0, op: w: Cannot modify private(set) property Test::$prop from global scope
Init: 1, scope: 0, op: rw: Cannot modify private(set) property Test::$prop from global scope
Init: 1, scope: 0, op: im: Cannot indirectly modify private(set) property Test::$array from global scope
Init: 1, scope: 0, op: is: 1
Init: 1, scope: 0, op: us: Cannot unset private(set) property Test::$prop from global scope
Init: 1, scope: 0, op: us_dim: Cannot indirectly modify private(set) property Test::$array from global scope
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: done
Init: 0, scope: 1, op: is: 0
Init: 0, scope: 1, op: us: done
Init: 0, scope: 1, op: us_dim: done
Init: 0, scope: 0, op: r: Typed property Test::$prop must not be accessed before initialization
Init: 0, scope: 0, op: w: Cannot modify private(set) 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 private(set) property Test::$array from global scope
Init: 0, scope: 0, op: is: 0
Init: 0, scope: 0, op: us: Cannot unset private(set) property Test::$prop from global scope
Init: 0, scope: 0, op: us_dim: done

View file

@ -0,0 +1,84 @@
--TEST--
Asymmetric visibility nested variations
--FILE--
<?php
class Inner {
public int $prop = 1;
public array $array = [];
}
class Test {
public private(set) 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';
}
function us_dim($test) {
unset($test->prop->array[0]);
echo 'done';
}
foreach ([true, false] as $init) {
foreach (['r', 'w', 'rw', 'im', 'is', 'us', 'us_dim'] 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: 1, op: us_dim: done
Init: 0, op: r: Typed property Test::$prop must not be accessed before initialization
Init: 0, op: w: Cannot indirectly modify private(set) property Test::$prop from global scope
Init: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
Init: 0, op: im: Cannot indirectly modify private(set) property Test::$prop from global scope
Init: 0, op: is: 0
Init: 0, op: us: done
Init: 0, op: us_dim: done

View file

@ -0,0 +1,13 @@
--TEST--
readonly property does not satisfy get/set abstract property
--FILE--
<?php
abstract class P {
protected abstract int $prop { get; set; }
}
class C extends P {
public function __construct(protected readonly int $prop) {}
}
?>
--EXPECTF--
Fatal error: Class C contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (P::$prop::set) in %s on line %d

View file

@ -1,5 +1,7 @@
--TEST-- --TEST--
readonly property does not satisfy get/set interface property readonly property does not satisfy get/set interface property
--DESCRIPTION--
The error message should be improved, the set access level comes from readonly.
--FILE-- --FILE--
<?php <?php
interface I { interface I {
@ -10,4 +12,4 @@ class C implements I {
} }
?> ?>
--EXPECTF-- --EXPECTF--
Fatal error: Class C contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (I::$prop::set) in %s on line %d Fatal error: Set access level of C::$prop must be omitted (as in class I) in %s on line %d

View file

@ -22,12 +22,10 @@ try {
} catch (Error $e) { } catch (Error $e) {
echo $e->getMessage(), "\n"; echo $e->getMessage(), "\n";
} }
try { $test->initProtected();
$test->initProtected(); var_dump($test);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$test = new B;
$test->initPrivate(); $test->initPrivate();
var_dump($test->prop); var_dump($test->prop);
@ -56,17 +54,20 @@ class Y extends X {
} }
$test = new Y; $test = new Y;
try { $test->initFromParent();
$test->initFromParent(); var_dump($test);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?> ?>
--EXPECT-- --EXPECTF--
Cannot initialize readonly property A::$prop from global scope Cannot modify protected(set) property A::$prop from global scope
Cannot initialize readonly property A::$prop from scope B object(B)#%d (1) {
["prop"]=>
int(2)
}
int(3) int(3)
int(1) int(1)
int(3) int(3)
Cannot initialize readonly property Y::$prop from scope X object(Y)#%d (1) {
["prop"]=>
int(1)
}

View file

@ -64,8 +64,8 @@ try {
--EXPECT-- --EXPECT--
bool(false) bool(false)
Typed property Test::$prop must not be accessed before initialization Typed property Test::$prop must not be accessed before initialization
Cannot initialize readonly property Test::$prop from global scope Cannot modify protected(set) property Test::$prop from global scope
Cannot unset readonly property Test::$prop from global scope Cannot unset protected(set) property Test::$prop from global scope
Test::__isset(prop) Test::__isset(prop)
bool(true) bool(true)
Test::__get(prop) Test::__get(prop)

View file

@ -0,0 +1,13 @@
--TEST--
public(set) is allowed on non-readonly
--FILE--
<?php
class A {
public public(set) int $prop;
}
?>
===DONE===
--EXPECT--
===DONE===

View file

@ -61,4 +61,4 @@ Test2::__get
int(1) int(1)
int(1) int(1)
Cannot unset readonly property Test2::$prop Cannot unset readonly property Test2::$prop
Cannot unset readonly property Test3::$prop from global scope Cannot unset protected(set) property Test3::$prop from global scope

View file

@ -5,9 +5,11 @@ Readonly variations
class Test { class Test {
public readonly int $prop; public readonly int $prop;
public readonly array $array;
public function init() { public function init() {
$this->prop = 1; $this->prop = 1;
$this->array = [];
} }
public function r() { public function r() {
@ -37,6 +39,11 @@ class Test {
unset($this->prop); unset($this->prop);
echo 'done'; echo 'done';
} }
public function us_dim() {
unset($this->array[0]);
echo 'done';
}
} }
function r($test) { function r($test) {
@ -67,9 +74,14 @@ function us($test) {
echo 'done'; echo 'done';
} }
function us_dim($test) {
unset($test->array[0]);
echo 'done';
}
foreach ([true, false] as $init) { foreach ([true, false] as $init) {
foreach ([true, false] as $scope) { foreach ([true, false] as $scope) {
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) { foreach (['r', 'w', 'rw', 'im', 'is', 'us', 'us_dim'] as $op) {
$test = new Test(); $test = new Test();
if ($init) { if ($init) {
$test->init(); $test->init();
@ -98,21 +110,25 @@ Init: 1, scope: 1, op: rw: Cannot modify readonly property Test::$prop
Init: 1, scope: 1, op: im: Cannot indirectly modify readonly property Test::$prop Init: 1, scope: 1, op: im: Cannot indirectly modify readonly property Test::$prop
Init: 1, scope: 1, op: is: 1 Init: 1, scope: 1, op: is: 1
Init: 1, scope: 1, op: us: Cannot unset readonly property Test::$prop Init: 1, scope: 1, op: us: Cannot unset readonly property Test::$prop
Init: 1, scope: 1, op: us_dim: Cannot indirectly modify readonly property Test::$array
Init: 1, scope: 0, op: r: 1 Init: 1, scope: 0, op: r: 1
Init: 1, scope: 0, op: w: Cannot modify readonly property Test::$prop 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: rw: Cannot modify readonly property Test::$prop
Init: 1, scope: 0, op: im: Cannot indirectly modify readonly property Test::$prop Init: 1, scope: 0, op: im: Cannot indirectly modify readonly property Test::$prop
Init: 1, scope: 0, op: is: 1 Init: 1, scope: 0, op: is: 1
Init: 1, scope: 0, op: us: Cannot unset readonly property Test::$prop Init: 1, scope: 0, op: us: Cannot unset readonly property Test::$prop
Init: 1, scope: 0, op: us_dim: Cannot indirectly modify readonly property Test::$array
Init: 0, scope: 1, op: r: Typed property Test::$prop must not be accessed before initialization 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: w: done
Init: 0, scope: 1, op: rw: Typed property Test::$prop must not be accessed before initialization 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: im: Cannot indirectly modify readonly property Test::$prop
Init: 0, scope: 1, op: is: 0 Init: 0, scope: 1, op: is: 0
Init: 0, scope: 1, op: us: done Init: 0, scope: 1, op: us: done
Init: 0, scope: 1, op: us_dim: done
Init: 0, scope: 0, op: r: Typed property Test::$prop must not be accessed before initialization 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: w: Cannot modify protected(set) 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: 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: im: Cannot indirectly modify readonly property Test::$prop
Init: 0, scope: 0, op: is: 0 Init: 0, scope: 0, op: is: 0
Init: 0, scope: 0, op: us: Cannot unset readonly property Test::$prop from global scope Init: 0, scope: 0, op: us: Cannot unset protected(set) property Test::$prop from global scope
Init: 0, scope: 0, op: us_dim: done

View file

@ -44,8 +44,13 @@ function us($test) {
echo 'done'; echo 'done';
} }
function us_dim($test) {
unset($test->prop->array[0]);
echo 'done';
}
foreach ([true, false] as $init) { foreach ([true, false] as $init) {
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) { foreach (['r', 'w', 'rw', 'im', 'is', 'us', 'us_dim'] as $op) {
$test = new Test(); $test = new Test();
if ($init) { if ($init) {
$test->init(); $test->init();
@ -69,9 +74,11 @@ Init: 1, op: rw: done
Init: 1, op: im: done Init: 1, op: im: done
Init: 1, op: is: 1 Init: 1, op: is: 1
Init: 1, op: us: done Init: 1, op: us: done
Init: 1, op: us_dim: done
Init: 0, op: r: Typed property Test::$prop must not be accessed before initialization 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: 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: 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: im: Cannot indirectly modify readonly property Test::$prop
Init: 0, op: is: 0 Init: 0, op: is: 0
Init: 0, op: us: done Init: 0, op: us: done
Init: 0, op: us_dim: done

View file

@ -4523,6 +4523,33 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
if (!(access_type & ZEND_ACC_PPP_MASK)) { if (!(access_type & ZEND_ACC_PPP_MASK)) {
access_type |= ZEND_ACC_PUBLIC; access_type |= ZEND_ACC_PUBLIC;
} }
/* Add the protected(set) bit for public readonly properties with no set visibility. */
if ((access_type & (ZEND_ACC_PUBLIC|ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK)) == (ZEND_ACC_PUBLIC|ZEND_ACC_READONLY)) {
access_type |= ZEND_ACC_PROTECTED_SET;
} else if (UNEXPECTED(access_type & ZEND_ACC_PPP_SET_MASK)) {
if (!ZEND_TYPE_IS_SET(type)) {
zend_error_noreturn(ce->type == ZEND_INTERNAL_CLASS ? E_CORE_ERROR : E_COMPILE_ERROR,
"Property with asymmetric visibility %s::$%s must have type",
ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
uint32_t get_visibility = zend_visibility_to_set_visibility(access_type & ZEND_ACC_PPP_MASK);
uint32_t set_visibility = access_type & ZEND_ACC_PPP_SET_MASK;
if (get_visibility > set_visibility) {
zend_error_noreturn(ce->type == ZEND_INTERNAL_CLASS ? E_CORE_ERROR : E_COMPILE_ERROR,
"Visibility of property %s::$%s must not be weaker than set visibility",
ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
/* Remove equivalent set visibility. */
if (((access_type & (ZEND_ACC_PUBLIC|ZEND_ACC_PUBLIC_SET)) == (ZEND_ACC_PUBLIC|ZEND_ACC_PUBLIC_SET))
|| ((access_type & (ZEND_ACC_PROTECTED|ZEND_ACC_PROTECTED_SET)) == (ZEND_ACC_PROTECTED|ZEND_ACC_PROTECTED_SET))
|| ((access_type & (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET)) == (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET))) {
access_type &= ~ZEND_ACC_PPP_SET_MASK;
}
/* private(set) properties are implicitly final. */
if (access_type & ZEND_ACC_PRIVATE_SET) {
access_type |= ZEND_ACC_FINAL;
}
}
/* Virtual properties have no backing storage, the offset should never be used. However, the /* Virtual properties have no backing storage, the offset should never be used. However, the
* virtual flag cannot be definitively determined at compile time. Allow using default values * virtual flag cannot be definitively determined at compile time. Allow using default values

View file

@ -1697,7 +1697,7 @@ static ZEND_COLD void zend_ast_export_attributes(smart_str *str, zend_ast *ast,
} }
} }
static ZEND_COLD void zend_ast_export_visibility(smart_str *str, uint32_t flags) { static ZEND_COLD void zend_ast_export_visibility(smart_str *str, uint32_t flags, zend_modifier_target target) {
if (flags & ZEND_ACC_PUBLIC) { if (flags & ZEND_ACC_PUBLIC) {
smart_str_appends(str, "public "); smart_str_appends(str, "public ");
} else if (flags & ZEND_ACC_PROTECTED) { } else if (flags & ZEND_ACC_PROTECTED) {
@ -1705,6 +1705,16 @@ static ZEND_COLD void zend_ast_export_visibility(smart_str *str, uint32_t flags)
} else if (flags & ZEND_ACC_PRIVATE) { } else if (flags & ZEND_ACC_PRIVATE) {
smart_str_appends(str, "private "); smart_str_appends(str, "private ");
} }
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
if (flags & ZEND_ACC_PRIVATE_SET) {
smart_str_appends(str, "private(set) ");
} else if (flags & ZEND_ACC_PROTECTED_SET) {
smart_str_appends(str, "protected(set) ");
} else if (flags & ZEND_ACC_PUBLIC_SET) {
smart_str_appends(str, "public(set) ");
}
}
} }
static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) { static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) {
@ -1815,7 +1825,7 @@ tail_call:
zend_ast_export_attributes(str, decl->child[4], indent, newlines); zend_ast_export_attributes(str, decl->child[4], indent, newlines);
} }
zend_ast_export_visibility(str, decl->flags); zend_ast_export_visibility(str, decl->flags, ZEND_MODIFIER_TARGET_METHOD);
if (decl->flags & ZEND_ACC_STATIC) { if (decl->flags & ZEND_ACC_STATIC) {
smart_str_appends(str, "static "); smart_str_appends(str, "static ");
@ -1941,7 +1951,7 @@ simple_list:
zend_ast_export_attributes(str, ast->child[2], indent, 1); zend_ast_export_attributes(str, ast->child[2], indent, 1);
} }
zend_ast_export_visibility(str, ast->attr); zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_PROPERTY);
if (ast->attr & ZEND_ACC_STATIC) { if (ast->attr & ZEND_ACC_STATIC) {
smart_str_appends(str, "static "); smart_str_appends(str, "static ");
@ -1967,7 +1977,7 @@ simple_list:
zend_ast_export_attributes(str, ast->child[1], indent, 1); zend_ast_export_attributes(str, ast->child[1], indent, 1);
} }
zend_ast_export_visibility(str, ast->attr); zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_CONSTANT);
smart_str_appends(str, "const "); smart_str_appends(str, "const ");
if (ast->child[2]) { if (ast->child[2]) {
zend_ast_export_type(str, ast->child[2], indent); zend_ast_export_type(str, ast->child[2], indent);
@ -2388,7 +2398,7 @@ simple_list:
for (uint32_t i = 0; i < hook_list->children; i++) { for (uint32_t i = 0; i < hook_list->children; i++) {
zend_ast_decl *hook = (zend_ast_decl *)hook_list->child[i]; zend_ast_decl *hook = (zend_ast_decl *)hook_list->child[i];
zend_ast_export_visibility(str, hook->flags); zend_ast_export_visibility(str, hook->flags, ZEND_MODIFIER_TARGET_PROPERTY);
if (hook->flags & ZEND_ACC_FINAL) { if (hook->flags & ZEND_ACC_FINAL) {
smart_str_appends(str, "final "); smart_str_appends(str, "final ");
} }

View file

@ -860,6 +860,12 @@ static char *zend_modifier_token_to_string(uint32_t token)
return "readonly"; return "readonly";
case T_ABSTRACT: case T_ABSTRACT:
return "abstract"; return "abstract";
case T_PUBLIC_SET:
return "public(set)";
case T_PROTECTED_SET:
return "protected(set)";
case T_PRIVATE_SET:
return "private(set)";
EMPTY_SWITCH_DEFAULT_CASE() EMPTY_SWITCH_DEFAULT_CASE()
} }
} }
@ -905,6 +911,21 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token
return ZEND_ACC_STATIC; return ZEND_ACC_STATIC;
} }
break; break;
case T_PUBLIC_SET:
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
return ZEND_ACC_PUBLIC_SET;
}
break;
case T_PROTECTED_SET:
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
return ZEND_ACC_PROTECTED_SET;
}
break;
case T_PRIVATE_SET:
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
return ZEND_ACC_PRIVATE_SET;
}
break;
} }
char *member; char *member;
@ -1020,6 +1041,13 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifi
"Cannot use the final modifier on an abstract method", 0); "Cannot use the final modifier on an abstract method", 0);
return 0; return 0;
} }
if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
if ((flags & ZEND_ACC_PPP_SET_MASK) && (new_flag & ZEND_ACC_PPP_SET_MASK)) {
zend_throw_exception(zend_ce_compile_error,
"Multiple access type modifiers are not allowed", 0);
return 0;
}
}
return new_flags; return new_flags;
} }
/* }}} */ /* }}} */
@ -7569,7 +7597,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast));
bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0;
bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0;
uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_READONLY); uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY);
bool is_promoted = property_flags || hooks_ast; bool is_promoted = property_flags || hooks_ast;
znode var_node, default_node; znode var_node, default_node;
@ -7793,7 +7821,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
zend_ast *param_ast = list->child[i]; zend_ast *param_ast = list->child[i];
zend_ast *hooks_ast = param_ast->child[5]; zend_ast *hooks_ast = param_ast->child[5];
bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0;
uint32_t flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_READONLY); uint32_t flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY);
bool is_promoted = flags || hooks_ast; bool is_promoted = flags || hooks_ast;
if (!is_promoted) { if (!is_promoted) {
continue; continue;

View file

@ -253,7 +253,7 @@ typedef struct _zend_oparray_context {
/* or IS_CONSTANT_VISITED_MARK | | | */ /* or IS_CONSTANT_VISITED_MARK | | | */
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
/* | | | */ /* | | | */
/* Property Flags (unused: 10...) | | | */ /* Property Flags (unused: 13...) | | | */
/* =========== | | | */ /* =========== | | | */
/* | | | */ /* | | | */
/* Promoted property / parameter | | | */ /* Promoted property / parameter | | | */
@ -262,6 +262,11 @@ typedef struct _zend_oparray_context {
/* Virtual property without backing storage | | | */ /* Virtual property without backing storage | | | */
#define ZEND_ACC_VIRTUAL (1 << 9) /* | | X | */ #define ZEND_ACC_VIRTUAL (1 << 9) /* | | X | */
/* | | | */ /* | | | */
/* Asymmetric visibility | | | */
#define ZEND_ACC_PUBLIC_SET (1 << 10) /* | | X | */
#define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */
#define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */
/* | | | */
/* Class Flags (unused: 30,31) | | | */ /* Class Flags (unused: 30,31) | | | */
/* =========== | | | */ /* =========== | | | */
/* | | | */ /* | | | */
@ -395,6 +400,20 @@ typedef struct _zend_oparray_context {
#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET)
static zend_always_inline uint32_t zend_visibility_to_set_visibility(uint32_t visibility)
{
switch (visibility) {
case ZEND_ACC_PUBLIC:
return ZEND_ACC_PUBLIC_SET;
case ZEND_ACC_PROTECTED:
return ZEND_ACC_PROTECTED_SET;
case ZEND_ACC_PRIVATE:
return ZEND_ACC_PRIVATE_SET;
EMPTY_SWITCH_DEFAULT_CASE();
}
}
/* call through internal function handler. e.g. Closure::invoke() */ /* call through internal function handler. e.g. Closure::invoke() */
#define ZEND_ACC_CALL_VIA_HANDLER ZEND_ACC_CALL_VIA_TRAMPOLINE #define ZEND_ACC_CALL_VIA_HANDLER ZEND_ACC_CALL_VIA_TRAMPOLINE
@ -1001,7 +1020,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
#define ZEND_FETCH_CLASS_ALLOW_UNLINKED 0x0400 #define ZEND_FETCH_CLASS_ALLOW_UNLINKED 0x0400
#define ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED 0x0800 #define ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED 0x0800
/* These should not clash with ZEND_ACC_(PUBLIC|PROTECTED|PRIVATE) */ /* These should not clash with ZEND_ACC_PPP_MASK and ZEND_ACC_PPP_SET_MASK */
#define ZEND_PARAM_REF (1<<3) #define ZEND_PARAM_REF (1<<3)
#define ZEND_PARAM_VARIADIC (1<<4) #define ZEND_PARAM_VARIADIC (1<<4)

View file

@ -915,6 +915,32 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_pr
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
} }
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibility_property_modification_error(
const zend_property_info *prop_info, const char *operation
) {
zend_class_entry *scope;
if (EG(fake_scope)) {
scope = EG(fake_scope);
} else {
scope = zend_get_called_scope(EG(current_execute_data));
}
const char *visibility;
if (prop_info->flags & ZEND_ACC_PRIVATE_SET) {
visibility = "private(set)";
} else {
ZEND_ASSERT(prop_info->flags & ZEND_ACC_PROTECTED_SET);
visibility = "protected(set)";
}
zend_throw_error(NULL, "Cannot %s %s property %s::$%s from %s%s",
operation,
visibility,
ZSTR_VAL(prop_info->ce->name),
ZSTR_VAL(prop_info->name),
scope ? "scope " : "global scope", scope ? ZSTR_VAL(scope->name) : "");
}
static const zend_class_entry *resolve_single_class_type(zend_string *name, const zend_class_entry *self_ce) { static const zend_class_entry *resolve_single_class_type(zend_string *name, const zend_class_entry *self_ce) {
if (zend_string_equals_literal_ci(name, "self")) { if (zend_string_equals_literal_ci(name, "self")) {
return self_ce; return self_ce;
@ -1028,9 +1054,15 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
{ {
zval tmp; zval tmp;
if (UNEXPECTED((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE))) { if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
zend_readonly_property_modification_error(info); if ((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) {
return &EG(uninitialized_zval); zend_readonly_property_modification_error(info);
return &EG(uninitialized_zval);
}
if (info->flags & ZEND_ACC_PPP_SET_MASK && !zend_asymmetric_property_has_set_access(info)) {
zend_asymmetric_visibility_property_modification_error(info, "modify");
return &EG(uninitialized_zval);
}
} }
ZVAL_DEREF(value); ZVAL_DEREF(value);
@ -3357,7 +3389,8 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
ZVAL_INDIRECT(result, ptr); ZVAL_INDIRECT(result, ptr);
zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2); zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2);
if (prop_info) { if (prop_info) {
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) { if (UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))
&& ((prop_info->flags & ZEND_ACC_READONLY) || !zend_asymmetric_property_has_set_access(prop_info))) {
/* For objects, W/RW/UNSET fetch modes might not actually modify object. /* For objects, W/RW/UNSET fetch modes might not actually modify object.
* Similar as with magic __get() allow them, but return the value as a copy * Similar as with magic __get() allow them, but return the value as a copy
* to make sure no actual modification is possible. */ * to make sure no actual modification is possible. */
@ -3365,7 +3398,11 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
if (Z_TYPE_P(ptr) == IS_OBJECT) { if (Z_TYPE_P(ptr) == IS_OBJECT) {
ZVAL_COPY(result, ptr); ZVAL_COPY(result, ptr);
} else { } else {
zend_readonly_property_indirect_modification_error(prop_info); if (prop_info->flags & ZEND_ACC_READONLY) {
zend_readonly_property_indirect_modification_error(prop_info);
} else {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
}
ZVAL_ERROR(result); ZVAL_ERROR(result);
} }
return; return;

View file

@ -92,6 +92,9 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_pr
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void);
ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_property_info *prop_info);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibility_property_modification_error(const zend_property_info *info, const char *operation);
ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); 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( ZEND_API ZEND_COLD void zend_verify_arg_error(
const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value); const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value);

View file

@ -212,6 +212,18 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */
} }
/* }}} */ /* }}} */
static const char *zend_asymmetric_visibility_string(uint32_t fn_flags) /* {{{ */
{
if (fn_flags & ZEND_ACC_PRIVATE_SET) {
return "private(set)";
} else if (fn_flags & ZEND_ACC_PROTECTED_SET) {
return "protected(set)";
} else {
ZEND_ASSERT(!(fn_flags & ZEND_ACC_PUBLIC_SET));
return "omitted";
}
}
static zend_string *resolve_class_name(zend_class_entry *scope, zend_string *name) { static zend_string *resolve_class_name(zend_class_entry *scope, zend_string *name) {
ZEND_ASSERT(scope); ZEND_ASSERT(scope);
if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { if (zend_string_equals_literal_ci(name, "parent") && scope->parent) {
@ -1442,6 +1454,25 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke
ZSTR_VAL(ce->name), ZSTR_VAL(key)); ZSTR_VAL(ce->name), ZSTR_VAL(key));
} }
} }
if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_SET_MASK))
/* Get-only virtual properties have no set visibility, so any child visibility is fine. */
&& !(parent_info->hooks && (parent_info->flags & ZEND_ACC_VIRTUAL) && !parent_info->hooks[ZEND_PROPERTY_HOOK_SET])) {
uint32_t parent_set_visibility = parent_info->flags & ZEND_ACC_PPP_SET_MASK;
/* Adding set protection is fine if it's the same or weaker than
* the parents full property visibility. */
if (!parent_set_visibility) {
parent_set_visibility = zend_visibility_to_set_visibility(parent_info->flags & ZEND_ACC_PPP_MASK);
}
uint32_t child_set_visibility = child_info->flags & ZEND_ACC_PPP_SET_MASK;
if (child_set_visibility > parent_set_visibility) {
zend_error_noreturn(
E_COMPILE_ERROR,
"Set access level of %s::$%s must be %s (as in class %s)%s",
ZSTR_VAL(ce->name), ZSTR_VAL(key),
zend_asymmetric_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name),
!(parent_info->flags & ZEND_ACC_PPP_SET_MASK) ? "" : " or weaker");
}
}
if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_MASK) > (parent_info->flags & ZEND_ACC_PPP_MASK))) { if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_MASK) > (parent_info->flags & ZEND_ACC_PPP_MASK))) {
zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker"); zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");

View file

@ -154,6 +154,9 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token <ident> T_PRIVATE "'private'" %token <ident> T_PRIVATE "'private'"
%token <ident> T_PROTECTED "'protected'" %token <ident> T_PROTECTED "'protected'"
%token <ident> T_PUBLIC "'public'" %token <ident> T_PUBLIC "'public'"
%token <ident> T_PRIVATE_SET "'private(set)'"
%token <ident> T_PROTECTED_SET "'protected(set)'"
%token <ident> T_PUBLIC_SET "'public(set)'"
%token <ident> T_READONLY "'readonly'" %token <ident> T_READONLY "'readonly'"
%token <ident> T_VAR "'var'" %token <ident> T_VAR "'var'"
%token <ident> T_UNSET "'unset'" %token <ident> T_UNSET "'unset'"
@ -1066,6 +1069,9 @@ member_modifier:
T_PUBLIC { $$ = T_PUBLIC; } T_PUBLIC { $$ = T_PUBLIC; }
| T_PROTECTED { $$ = T_PROTECTED; } | T_PROTECTED { $$ = T_PROTECTED; }
| T_PRIVATE { $$ = T_PRIVATE; } | T_PRIVATE { $$ = T_PRIVATE; }
| T_PUBLIC_SET { $$ = T_PUBLIC_SET; }
| T_PROTECTED_SET { $$ = T_PROTECTED_SET; }
| T_PRIVATE_SET { $$ = T_PRIVATE_SET; }
| T_STATIC { $$ = T_STATIC; } | T_STATIC { $$ = T_STATIC; }
| T_ABSTRACT { $$ = T_ABSTRACT; } | T_ABSTRACT { $$ = T_ABSTRACT; }
| T_FINAL { $$ = T_FINAL; } | T_FINAL { $$ = T_FINAL; }

View file

@ -1725,6 +1725,18 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN_WITH_IDENT(T_PROTECTED); RETURN_TOKEN_WITH_IDENT(T_PROTECTED);
} }
<ST_IN_SCRIPTING>"public(set)" {
RETURN_TOKEN_WITH_IDENT(T_PUBLIC_SET);
}
<ST_IN_SCRIPTING>"protected(set)" {
RETURN_TOKEN_WITH_IDENT(T_PROTECTED_SET);
}
<ST_IN_SCRIPTING>"private(set)" {
RETURN_TOKEN_WITH_IDENT(T_PRIVATE_SET);
}
<ST_IN_SCRIPTING>"public" { <ST_IN_SCRIPTING>"public" {
RETURN_TOKEN_WITH_IDENT(T_PUBLIC); RETURN_TOKEN_WITH_IDENT(T_PUBLIC);
} }

View file

@ -304,13 +304,6 @@ static ZEND_COLD zend_never_inline bool zend_deprecated_dynamic_property(
return 1; return 1;
} }
static ZEND_COLD zend_never_inline void zend_readonly_property_modification_scope_error(
const zend_class_entry *ce, const zend_string *member, const zend_class_entry *scope, const char *operation) {
zend_throw_error(NULL, "Cannot %s readonly property %s::$%s from %s%s",
operation, ZSTR_VAL(ce->name), ZSTR_VAL(member),
scope ? "scope " : "global scope", scope ? ZSTR_VAL(scope->name) : "");
}
static ZEND_COLD zend_never_inline void zend_readonly_property_unset_error( static ZEND_COLD zend_never_inline void zend_readonly_property_unset_error(
zend_class_entry *ce, zend_string *member) { zend_class_entry *ce, zend_string *member) {
zend_throw_error(NULL, "Cannot unset readonly property %s::$%s", zend_throw_error(NULL, "Cannot unset readonly property %s::$%s",
@ -547,6 +540,17 @@ ZEND_API zend_result zend_check_property_access(const zend_object *zobj, zend_st
} }
/* }}} */ /* }}} */
ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_property_info *prop_info) {
ZEND_ASSERT(prop_info->flags & ZEND_ACC_PPP_SET_MASK);
ZEND_ASSERT(!(prop_info->flags & ZEND_ACC_PUBLIC_SET));
zend_class_entry *scope = get_fake_or_executed_scope();
if (prop_info->ce == scope) {
return true;
}
return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET)
&& is_protected_compatible_scope(prop_info->ce, scope));
}
static void zend_property_guard_dtor(zval *el) /* {{{ */ { static void zend_property_guard_dtor(zval *el) /* {{{ */ {
uint32_t *ptr = (uint32_t*)Z_PTR_P(el); uint32_t *ptr = (uint32_t*)Z_PTR_P(el);
if (EXPECTED(!(((uintptr_t)ptr) & 1))) { if (EXPECTED(!(((uintptr_t)ptr) & 1))) {
@ -686,32 +690,31 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
try_again: try_again:
retval = OBJ_PROP(zobj, property_offset); retval = OBJ_PROP(zobj, property_offset);
if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) {
if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY) if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))
&& (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) { && (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)
if (Z_TYPE_P(retval) == IS_OBJECT) { && ((prop_info->flags & ZEND_ACC_READONLY) || !zend_asymmetric_property_has_set_access(prop_info))) {
/* For objects, W/RW/UNSET fetch modes might not actually modify object. if (Z_TYPE_P(retval) == IS_OBJECT) {
* Similar as with magic __get() allow them, but return the value as a copy /* For objects, W/RW/UNSET fetch modes might not actually modify object.
* to make sure no actual modification is possible. */ * Similar as with magic __get() allow them, but return the value as a copy
ZVAL_COPY(rv, retval); * to make sure no actual modification is possible. */
retval = rv; ZVAL_COPY(rv, retval);
} else { retval = rv;
zend_readonly_property_indirect_modification_error(prop_info); goto exit;
retval = &EG(uninitialized_zval); } else if (Z_TYPE_P(retval) == IS_UNDEF && type == BP_VAR_UNSET) {
} retval = &EG(uninitialized_zval);
goto exit;
} }
if (prop_info->flags & ZEND_ACC_READONLY) {
zend_readonly_property_indirect_modification_error(prop_info);
} else {
zend_asymmetric_visibility_property_modification_error(prop_info, "indirectly modify");
}
retval = &EG(uninitialized_zval);
goto exit;
}
if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) {
goto exit; 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)) { if (UNEXPECTED(Z_PROP_FLAG_P(retval) & IS_PROP_UNINIT)) {
/* Skip __get() for uninitialized typed properties */ /* Skip __get() for uninitialized typed properties */
@ -912,36 +915,12 @@ static zend_always_inline bool property_uses_strict_types(void) {
&& ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)); && ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data));
} }
static bool verify_readonly_initialization_access(
const zend_property_info *prop_info, const zend_class_entry *ce,
zend_string *name, const char *operation) {
zend_class_entry *scope = get_fake_or_executed_scope();
if (prop_info->ce == scope) {
return true;
}
/* We may have redeclared a parent property. In that case the parent should still be
* allowed to initialize it. */
if (scope && is_derived_class(ce, scope)) {
const zend_property_info *prop_info = zend_hash_find_ptr(&scope->properties_info, name);
if (prop_info) {
/* This should be ensured by inheritance. */
ZEND_ASSERT(prop_info->flags & ZEND_ACC_READONLY);
if (prop_info->ce == scope) {
return true;
}
}
}
zend_readonly_property_modification_scope_error(prop_info->ce, name, scope, operation);
return false;
}
ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zval *value, void **cache_slot) /* {{{ */ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zval *value, void **cache_slot) /* {{{ */
{ {
zval *variable_ptr, tmp; zval *variable_ptr, tmp;
uintptr_t property_offset; uintptr_t property_offset;
const zend_property_info *prop_info = NULL; const zend_property_info *prop_info = NULL;
uint32_t *guard = NULL;
ZEND_ASSERT(!Z_ISREF_P(value)); ZEND_ASSERT(!Z_ISREF_P(value));
property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info);
@ -949,17 +928,35 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
try_again: try_again:
variable_ptr = OBJ_PROP(zobj, property_offset); variable_ptr = OBJ_PROP(zobj, property_offset);
if (Z_TYPE_P(variable_ptr) != IS_UNDEF) {
Z_TRY_ADDREF_P(value);
if (prop_info) { if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINITABLE))) { bool error;
Z_TRY_DELREF_P(value); if (Z_TYPE_P(variable_ptr) != IS_UNDEF || (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) || !zobj->ce->__set) {
error = true;
} else {
guard = zend_get_property_guard(zobj, name);
error = (*guard) & IN_SET;
}
if (error) {
if ((prop_info->flags & ZEND_ACC_READONLY)
&& Z_TYPE_P(variable_ptr) != IS_UNDEF
&& !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINITABLE)) {
zend_readonly_property_modification_error(prop_info); zend_readonly_property_modification_error(prop_info);
variable_ptr = &EG(error_zval); variable_ptr = &EG(error_zval);
goto exit; goto exit;
} }
if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) {
zend_asymmetric_visibility_property_modification_error(prop_info, "modify");
variable_ptr = &EG(error_zval);
goto exit;
}
}
}
if (Z_TYPE_P(variable_ptr) != IS_UNDEF) {
Z_TRY_ADDREF_P(value);
if (prop_info) {
typed_property: typed_property:
ZVAL_COPY_VALUE(&tmp, value); ZVAL_COPY_VALUE(&tmp, value);
// Increase refcount to prevent object from being released in __toString() // Increase refcount to prevent object from being released in __toString()
@ -1073,7 +1070,9 @@ found:;
/* magic set */ /* magic set */
if (zobj->ce->__set) { if (zobj->ce->__set) {
uint32_t *guard = zend_get_property_guard(zobj, name); if (!guard) {
guard = zend_get_property_guard(zobj, name);
}
if (!((*guard) & IN_SET)) { if (!((*guard) & IN_SET)) {
GC_ADDREF(zobj); GC_ADDREF(zobj);
@ -1099,12 +1098,6 @@ write_std_property:
Z_TRY_ADDREF_P(value); Z_TRY_ADDREF_P(value);
if (prop_info) { if (prop_info) {
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY)
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize"))) {
Z_TRY_DELREF_P(value);
variable_ptr = &EG(error_zval);
goto exit;
}
goto typed_property; goto typed_property;
} }
@ -1270,9 +1263,10 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
ZVAL_NULL(retval); ZVAL_NULL(retval);
} }
} }
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) { } else if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
/* Readonly property, delegate to read_property + write_property. */ if ((prop_info->flags & ZEND_ACC_READONLY) || !zend_asymmetric_property_has_set_access(prop_info)) {
retval = NULL; retval = NULL;
}
} else if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) { } else if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
ZVAL_NULL(retval); ZVAL_NULL(retval);
} }
@ -1280,9 +1274,10 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
/* we do have getter - fail and let it try again with usual get/set */ /* we do have getter - fail and let it try again with usual get/set */
retval = NULL; retval = NULL;
} }
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) { } else if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
/* Readonly property, delegate to read_property + write_property. */ if ((prop_info->flags & ZEND_ACC_READONLY) || !zend_asymmetric_property_has_set_access(prop_info)) {
retval = NULL; retval = NULL;
}
} }
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) { } else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) {
if (EXPECTED(zobj->properties)) { if (EXPECTED(zobj->properties)) {
@ -1327,21 +1322,36 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
{ {
uintptr_t property_offset; uintptr_t property_offset;
const zend_property_info *prop_info = NULL; const zend_property_info *prop_info = NULL;
uint32_t *guard = NULL;
property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__unset != NULL), cache_slot, &prop_info); property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__unset != NULL), cache_slot, &prop_info);
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
zval *slot = OBJ_PROP(zobj, property_offset); zval *slot = OBJ_PROP(zobj, property_offset);
if (Z_TYPE_P(slot) != IS_UNDEF) { if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY))) { bool error;
if (Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE) { if (Z_TYPE_P(slot) != IS_UNDEF || Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT || !zobj->ce->__unset) {
Z_PROP_FLAG_P(slot) &= ~IS_PROP_REINITABLE; error = true;
} else { } else {
guard = zend_get_property_guard(zobj, name);
error = (*guard) & IN_UNSET;
}
if (error) {
if ((prop_info->flags & ZEND_ACC_READONLY)
&& Z_TYPE_P(slot) != IS_UNDEF
&& !(Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE)) {
zend_readonly_property_unset_error(prop_info->ce, name); zend_readonly_property_unset_error(prop_info->ce, name);
return; return;
} }
if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) {
zend_asymmetric_visibility_property_modification_error(prop_info, "unset");
return;
}
} }
}
if (Z_TYPE_P(slot) != IS_UNDEF) {
if (UNEXPECTED(Z_ISREF_P(slot)) && if (UNEXPECTED(Z_ISREF_P(slot)) &&
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) { (ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) {
if (prop_info) { if (prop_info) {
@ -1358,11 +1368,6 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
return; return;
} }
if (UNEXPECTED(Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT)) { if (UNEXPECTED(Z_PROP_FLAG_P(slot) & IS_PROP_UNINIT)) {
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY)
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "unset"))) {
return;
}
/* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */ /* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */
Z_PROP_FLAG_P(slot) = 0; Z_PROP_FLAG_P(slot) = 0;
return; return;
@ -1388,7 +1393,9 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
/* magic unset */ /* magic unset */
if (zobj->ce->__unset) { if (zobj->ce->__unset) {
uint32_t *guard = zend_get_property_guard(zobj, name); if (!guard) {
guard = zend_get_property_guard(zobj, name);
}
if (!((*guard) & IN_UNSET)) { if (!((*guard) & IN_UNSET)) {
/* have unsetter - try with it! */ /* have unsetter - try with it! */
(*guard) |= IN_UNSET; /* prevent circular unsetting */ (*guard) |= IN_UNSET; /* prevent circular unsetting */

View file

@ -310,6 +310,8 @@ ZEND_API zend_function *zend_get_property_hook_trampoline(
const zend_property_info *prop_info, const zend_property_info *prop_info,
zend_property_hook_kind kind, zend_string *prop_name); zend_property_hook_kind kind, zend_string *prop_name);
ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_property_info *prop_info);
#define zend_release_properties(ht) do { \ #define zend_release_properties(ht) do { \
if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \ if ((ht) && !(GC_FLAGS(ht) & GC_IMMUTABLE) && !GC_DELREF(ht)) { \
zend_array_destroy(ht); \ zend_array_destroy(ht); \

View file

@ -2648,6 +2648,22 @@ static void ZEND_FASTCALL zend_jit_assign_obj_helper(zend_object *zobj, zend_str
} }
} }
static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend_property_info *info, bool indirect)
{
if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) {
if ((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) {
zend_readonly_property_modification_error(info);
return false;
}
if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) {
const char *operation = indirect ? "indirectly modify" : "modify";
zend_asymmetric_visibility_property_modification_error(info, operation);
return false;
}
}
return true;
}
static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result) static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result)
{ {
zend_execute_data *execute_data = EG(current_execute_data); zend_execute_data *execute_data = EG(current_execute_data);
@ -2661,8 +2677,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend
value = &EG(uninitialized_zval); value = &EG(uninitialized_zval);
} }
if (UNEXPECTED((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(property_val, info, false))) {
zend_readonly_property_modification_error(info);
if (result) { if (result) {
ZVAL_UNDEF(result); ZVAL_UNDEF(result);
} }
@ -2723,8 +2738,7 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_prop
zend_execute_data *execute_data = EG(current_execute_data); zend_execute_data *execute_data = EG(current_execute_data);
zval z_copy; zval z_copy;
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(zptr) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true))) {
zend_readonly_property_modification_error(prop_info);
return; return;
} }
@ -2818,8 +2832,7 @@ static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_i
{ {
ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF);
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(var_ptr) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) {
zend_readonly_property_modification_error(prop_info);
return; return;
} }
@ -2851,8 +2864,7 @@ static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_i
{ {
ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF);
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(var_ptr) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) {
zend_readonly_property_modification_error(prop_info);
return; return;
} }
@ -2898,8 +2910,7 @@ static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_prope
{ {
ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF);
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(var_ptr) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) {
zend_readonly_property_modification_error(prop_info);
if (result) { if (result) {
ZVAL_UNDEF(result); ZVAL_UNDEF(result);
} }
@ -2933,8 +2944,7 @@ static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_prope
{ {
ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF);
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(var_ptr) & IS_PROP_REINITABLE))) { if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) {
zend_readonly_property_modification_error(prop_info);
if (result) { if (result) {
ZVAL_UNDEF(result); ZVAL_UNDEF(result);
} }

View file

@ -13933,16 +13933,37 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
&& (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT)))) { && (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT)))) {
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS; uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
ir_ref allowed_inputs = IR_UNUSED;
ir_ref forbidden_inputs = IR_UNUSED;
ir_ref prop_info_ref = ir_LOAD_A( ir_ref prop_info_ref = ir_LOAD_A(
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2)); ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2));
ir_ref if_has_prop_info = ir_IF(prop_info_ref); ir_ref if_has_prop_info = ir_IF(prop_info_ref);
ir_IF_TRUE_cold(if_has_prop_info); ir_IF_TRUE_cold(if_has_prop_info);
ir_ref if_readonly = ir_IF( ir_ref prop_flags = ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, flags)));
ir_AND_U32(ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, flags))), ir_ref if_readonly_or_avis = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK)));
ir_CONST_U32(ZEND_ACC_READONLY)));
ir_IF_FALSE(if_readonly_or_avis);
ir_END_list(allowed_inputs);
ir_IF_TRUE_cold(if_readonly_or_avis);
ir_ref if_readonly = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY)));
ir_IF_TRUE(if_readonly); ir_IF_TRUE(if_readonly);
ir_END_list(forbidden_inputs);
ir_IF_FALSE(if_readonly);
ir_ref has_avis_access = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_asymmetric_property_has_set_access), prop_info_ref);
ir_ref if_avis_access = ir_IF(has_avis_access);
ir_IF_TRUE(if_avis_access);
ir_END_list(allowed_inputs);
ir_IF_FALSE(if_avis_access);
ir_END_list(forbidden_inputs);
ir_MERGE_list(forbidden_inputs);
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT); ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
ir_IF_TRUE(if_prop_obj); ir_IF_TRUE(if_prop_obj);
@ -13955,19 +13976,27 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ir_IF_FALSE_cold(if_prop_obj); ir_IF_FALSE_cold(if_prop_obj);
jit_SET_EX_OPLINE(jit, opline); jit_SET_EX_OPLINE(jit, opline);
if_readonly = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY)));
ir_IF_TRUE(if_readonly);
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), prop_info_ref); ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), prop_info_ref);
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR); jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
ir_END_list(end_inputs); ir_END_list(end_inputs);
ir_IF_FALSE(if_readonly);
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_asymmetric_visibility_property_modification_error),
prop_info_ref, ir_CONST_ADDR("indirectly modify"));
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
ir_END_list(end_inputs);
ir_MERGE_list(allowed_inputs);
if (flags == ZEND_FETCH_DIM_WRITE) { if (flags == ZEND_FETCH_DIM_WRITE) {
ir_IF_FALSE_cold(if_readonly);
jit_SET_EX_OPLINE(jit, opline); jit_SET_EX_OPLINE(jit, opline);
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion), ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion),
prop_ref, prop_info_ref); prop_ref, prop_info_ref);
ir_END_list(end_inputs); ir_END_list(end_inputs);
ir_IF_FALSE(if_has_prop_info); ir_IF_FALSE(if_has_prop_info);
} else if (flags == ZEND_FETCH_REF) { } else if (flags == ZEND_FETCH_REF) {
ir_IF_FALSE_cold(if_readonly);
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref), ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref),
prop_ref, prop_ref,
prop_info_ref, prop_info_ref,
@ -13976,10 +14005,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ir_IF_FALSE(if_has_prop_info); ir_IF_FALSE(if_has_prop_info);
} else { } else {
ZEND_ASSERT(flags == 0); ZEND_ASSERT(flags == 0);
ir_IF_FALSE(if_has_prop_info); ir_MERGE_WITH_EMPTY_FALSE(if_has_prop_info);
ir_ref no_prop_info_path = ir_END();
ir_IF_FALSE(if_readonly);
ir_MERGE_WITH(no_prop_info_path);
} }
} }
} else { } else {
@ -14005,9 +14031,6 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ir_IF_TRUE(if_def); ir_IF_TRUE(if_def);
} }
if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) { if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) {
if (!prop_type_ref) {
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
}
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT); ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
ir_IF_TRUE(if_prop_obj); ir_IF_TRUE(if_prop_obj);
ir_ref ref = jit_Z_PTR(jit, prop_addr); ir_ref ref = jit_Z_PTR(jit, prop_addr);
@ -14023,6 +14046,28 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
ir_END_list(end_inputs); ir_END_list(end_inputs);
goto result_fetched; goto result_fetched;
} else if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_PPP_SET_MASK)) {
/* Readonly properties which are also asymmetric are never mutable indirectly, which is
* handled by the previous branch. */
ir_ref has_access = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_asymmetric_property_has_set_access), ir_CONST_ADDR(prop_info));
ir_ref if_access = ir_IF(has_access);
ir_IF_FALSE_cold(if_access);
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
ir_IF_TRUE(if_prop_obj);
ir_ref ref = jit_Z_PTR(jit, prop_addr);
jit_GC_ADDREF(jit, ref);
jit_set_Z_PTR(jit, res_addr, ref);
jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX);
ir_END_list(end_inputs);
ir_IF_FALSE_cold(if_prop_obj);
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_asymmetric_visibility_property_modification_error),
ir_CONST_ADDR(prop_info), ir_CONST_ADDR("indirectly modify"));
ir_END_list(end_inputs);
ir_IF_TRUE(if_access);
} }
if (opline->opcode == ZEND_FETCH_OBJ_W if (opline->opcode == ZEND_FETCH_OBJ_W

View file

@ -940,6 +940,17 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha
smart_str_appends(str, "protected "); smart_str_appends(str, "protected ");
break; break;
} }
switch (prop->flags & ZEND_ACC_PPP_SET_MASK) {
case ZEND_ACC_PRIVATE_SET:
smart_str_appends(str, "private(set) ");
break;
case ZEND_ACC_PROTECTED_SET:
smart_str_appends(str, "protected(set) ");
break;
case ZEND_ACC_PUBLIC_SET:
ZEND_UNREACHABLE();
break;
}
if (prop->flags & ZEND_ACC_STATIC) { if (prop->flags & ZEND_ACC_STATIC) {
smart_str_appends(str, "static "); smart_str_appends(str, "static ");
} }
@ -5679,6 +5690,16 @@ ZEND_METHOD(ReflectionProperty, isProtected)
} }
/* }}} */ /* }}} */
ZEND_METHOD(ReflectionProperty, isPrivateSet)
{
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_PRIVATE_SET);
}
ZEND_METHOD(ReflectionProperty, isProtectedSet)
{
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_PROTECTED_SET);
}
/* {{{ Returns whether this property is static */ /* {{{ Returns whether this property is static */
ZEND_METHOD(ReflectionProperty, isStatic) ZEND_METHOD(ReflectionProperty, isStatic)
{ {
@ -5727,7 +5748,7 @@ ZEND_METHOD(ReflectionProperty, getModifiers)
{ {
reflection_object *intern; reflection_object *intern;
property_reference *ref; property_reference *ref;
uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT | ZEND_ACC_VIRTUAL; uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT | ZEND_ACC_VIRTUAL;
if (zend_parse_parameters_none() == FAILURE) { if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS(); RETURN_THROWS();

View file

@ -444,6 +444,10 @@ class ReflectionProperty implements Reflector
public const int IS_PRIVATE = UNKNOWN; public const int IS_PRIVATE = UNKNOWN;
/** @cvalue ZEND_ACC_ABSTRACT */ /** @cvalue ZEND_ACC_ABSTRACT */
public const int IS_ABSTRACT = UNKNOWN; public const int IS_ABSTRACT = UNKNOWN;
/** @cvalue ZEND_ACC_PROTECTED_SET */
public const int IS_PROTECTED_SET = UNKNOWN;
/** @cvalue ZEND_ACC_PRIVATE_SET */
public const int IS_PRIVATE_SET = UNKNOWN;
public string $name; public string $name;
public string $class; public string $class;
@ -480,6 +484,10 @@ class ReflectionProperty implements Reflector
/** @tentative-return-type */ /** @tentative-return-type */
public function isProtected(): bool {} public function isProtected(): bool {}
public function isPrivateSet(): bool {}
public function isProtectedSet(): bool {}
/** @tentative-return-type */ /** @tentative-return-type */
public function isStatic(): bool {} public function isStatic(): bool {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* This is a generated file, edit the .stub.php file instead.
* Stub hash: d2a365f6f398bbcf3f2520c28d508ca63f08b5ff */ * Stub hash: 28fde6ed0e247201ee25d608d27a4c5b0bb8f2f7 */
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@ -371,6 +371,10 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionProperty_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace
#define arginfo_class_ReflectionProperty_isPrivateSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
#define arginfo_class_ReflectionProperty_isProtectedSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
#define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace
#define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
@ -785,6 +789,8 @@ ZEND_METHOD(ReflectionProperty, isInitialized);
ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPublic);
ZEND_METHOD(ReflectionProperty, isPrivate); ZEND_METHOD(ReflectionProperty, isPrivate);
ZEND_METHOD(ReflectionProperty, isProtected); ZEND_METHOD(ReflectionProperty, isProtected);
ZEND_METHOD(ReflectionProperty, isPrivateSet);
ZEND_METHOD(ReflectionProperty, isProtectedSet);
ZEND_METHOD(ReflectionProperty, isStatic); ZEND_METHOD(ReflectionProperty, isStatic);
ZEND_METHOD(ReflectionProperty, isReadOnly); ZEND_METHOD(ReflectionProperty, isReadOnly);
ZEND_METHOD(ReflectionProperty, isDefault); ZEND_METHOD(ReflectionProperty, isDefault);
@ -1078,6 +1084,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = {
ZEND_ME(ReflectionProperty, isPublic, arginfo_class_ReflectionProperty_isPublic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPublic, arginfo_class_ReflectionProperty_isPublic, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isPrivateSet, arginfo_class_ReflectionProperty_isPrivateSet, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isProtectedSet, arginfo_class_ReflectionProperty_isProtectedSet, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC)
@ -1515,6 +1523,18 @@ static zend_class_entry *register_class_ReflectionProperty(zend_class_entry *cla
zend_declare_typed_class_constant(class_entry, const_IS_ABSTRACT_name, &const_IS_ABSTRACT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_declare_typed_class_constant(class_entry, const_IS_ABSTRACT_name, &const_IS_ABSTRACT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_IS_ABSTRACT_name); zend_string_release(const_IS_ABSTRACT_name);
zval const_IS_PROTECTED_SET_value;
ZVAL_LONG(&const_IS_PROTECTED_SET_value, ZEND_ACC_PROTECTED_SET);
zend_string *const_IS_PROTECTED_SET_name = zend_string_init_interned("IS_PROTECTED_SET", sizeof("IS_PROTECTED_SET") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_IS_PROTECTED_SET_name, &const_IS_PROTECTED_SET_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_IS_PROTECTED_SET_name);
zval const_IS_PRIVATE_SET_value;
ZVAL_LONG(&const_IS_PRIVATE_SET_value, ZEND_ACC_PRIVATE_SET);
zend_string *const_IS_PRIVATE_SET_name = zend_string_init_interned("IS_PRIVATE_SET", sizeof("IS_PRIVATE_SET") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_IS_PRIVATE_SET_name, &const_IS_PRIVATE_SET_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_IS_PRIVATE_SET_name);
zval property_name_default_value; zval property_name_default_value;
ZVAL_UNDEF(&property_name_default_value); ZVAL_UNDEF(&property_name_default_value);
zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);

View file

@ -25,8 +25,8 @@ Class [ <user> readonly class Foo ] {
} }
- Properties [2] { - Properties [2] {
Property [ public readonly int $bar ] Property [ public protected(set) readonly int $bar ]
Property [ public readonly int $baz ] Property [ public protected(set) readonly int $baz ]
} }
- Methods [0] { - Methods [0] {

View file

@ -31,7 +31,7 @@ Class [ <user> final class Foo implements UnitEnum ] {
} }
- Properties [1] { - Properties [1] {
Property [ public readonly string $name ] Property [ public protected(set) readonly string $name ]
} }
- Methods [0] { - Methods [0] {

View file

@ -31,7 +31,7 @@ Class [ <user> final class Foo implements UnitEnum ] {
} }
- Properties [1] { - Properties [1] {
Property [ public readonly string $name ] Property [ public protected(set) readonly string $name ]
} }
- Methods [0] { - Methods [0] {

View file

@ -0,0 +1,34 @@
--TEST--
ReflectionProperty::is{Private,Protected}Set
--FILE--
<?php
class Foo {
public private(set) int $bar;
public protected(set) int $baz;
}
function test($property) {
$reflectionProperty = new ReflectionProperty(Foo::class, $property);
var_dump($reflectionProperty->isPrivateSet());
var_dump($reflectionProperty->isProtectedSet());
var_dump(($reflectionProperty->getModifiers() & ReflectionProperty::IS_PRIVATE_SET) !== 0);
var_dump(($reflectionProperty->getModifiers() & ReflectionProperty::IS_PROTECTED_SET) !== 0);
echo $reflectionProperty;
}
test('bar');
test('baz');
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(false)
Property [ public private(set) int $bar ]
bool(false)
bool(true)
bool(false)
bool(true)
Property [ public protected(set) int $baz ]

View file

@ -0,0 +1,25 @@
--TEST--
ReflectionProperty::is{Private,Protected}Set
--FILE--
<?php
class Foo {
public private(set) int $bar = 0;
public protected(set) int $baz = 0;
}
function test($property) {
static $i = 0;
$foo = new Foo();
$reflectionProperty = new ReflectionProperty(Foo::class, $property);
$reflectionProperty->setValue($foo, $i++);
var_dump($reflectionProperty->getValue($foo));
}
test('bar');
test('baz');
?>
--EXPECT--
int(0)
int(1)

View file

@ -25,4 +25,4 @@ bool(false)
bool(false) bool(false)
bool(true) bool(true)
bool(true) bool(true)
Property [ public readonly int $ro ] Property [ public protected(set) readonly int $ro ]

View file

@ -36,8 +36,8 @@ Class [ <internal%s> class Directory ] {
} }
- Properties [2] { - Properties [2] {
Property [ public readonly string $path ] Property [ public protected(set) readonly string $path ]
Property [ public readonly mixed $handle ] Property [ public protected(set) readonly mixed $handle ]
} }
- Methods [3] { - Methods [3] {

View file

@ -92,6 +92,9 @@ char *get_token_type_name(int token_type)
case T_PRIVATE: return "T_PRIVATE"; case T_PRIVATE: return "T_PRIVATE";
case T_PROTECTED: return "T_PROTECTED"; case T_PROTECTED: return "T_PROTECTED";
case T_PUBLIC: return "T_PUBLIC"; case T_PUBLIC: return "T_PUBLIC";
case T_PRIVATE_SET: return "T_PRIVATE_SET";
case T_PROTECTED_SET: return "T_PROTECTED_SET";
case T_PUBLIC_SET: return "T_PUBLIC_SET";
case T_READONLY: return "T_READONLY"; case T_READONLY: return "T_READONLY";
case T_VAR: return "T_VAR"; case T_VAR: return "T_VAR";
case T_UNSET: return "T_UNSET"; case T_UNSET: return "T_UNSET";

View file

@ -337,6 +337,21 @@ const T_PROTECTED = UNKNOWN;
* @cvalue T_PUBLIC * @cvalue T_PUBLIC
*/ */
const T_PUBLIC = UNKNOWN; const T_PUBLIC = UNKNOWN;
/**
* @var int
* @cvalue T_PRIVATE_SET
*/
const T_PRIVATE_SET = UNKNOWN;
/**
* @var int
* @cvalue T_PROTECTED_SET
*/
const T_PROTECTED_SET = UNKNOWN;
/**
* @var int
* @cvalue T_PUBLIC_SET
*/
const T_PUBLIC_SET = UNKNOWN;
/** /**
* @var int * @var int
* @cvalue T_READONLY * @cvalue T_READONLY

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* This is a generated file, edit the .stub.php file instead.
* Stub hash: b7a13b3b242bd535f62a7d819eb0df751efb54ed */ * Stub hash: d917cab61a2b436a16d2227cdb438add45e42d69 */
static void register_tokenizer_data_symbols(int module_number) static void register_tokenizer_data_symbols(int module_number)
{ {
@ -70,6 +70,9 @@ static void register_tokenizer_data_symbols(int module_number)
REGISTER_LONG_CONSTANT("T_PRIVATE", T_PRIVATE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PRIVATE", T_PRIVATE, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PROTECTED", T_PROTECTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PROTECTED", T_PROTECTED, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PUBLIC", T_PUBLIC, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PUBLIC", T_PUBLIC, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PRIVATE_SET", T_PRIVATE_SET, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PROTECTED_SET", T_PROTECTED_SET, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PUBLIC_SET", T_PUBLIC_SET, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_READONLY", T_READONLY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_READONLY", T_READONLY, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_VAR", T_VAR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_VAR", T_VAR, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_UNSET", T_UNSET, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_UNSET", T_UNSET, CONST_PERSISTENT);