diff --git a/Zend/tests/errmsg_038.phpt b/Zend/tests/errmsg_038.phpt deleted file mode 100644 index d5ae50ded0f92..0000000000000 --- a/Zend/tests/errmsg_038.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -errmsg: properties cannot be final ---FILE-- - ---EXPECTF-- -Fatal error: Cannot declare property test::$var final, the final modifier is allowed only for methods and classes in %s on line %d diff --git a/Zend/tests/final_properties/access/lazy_initialization.phpt b/Zend/tests/final_properties/access/lazy_initialization.phpt new file mode 100644 index 0000000000000..ead3273846b5f --- /dev/null +++ b/Zend/tests/final_properties/access/lazy_initialization.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test that final properties can be initialized lazily by unsetting them and using __get() +--FILE-- +property1); + unset($this->property2); + unset($this->property3); + } + + public function __get($name) + { + switch ($name) { + case "property1": + if ($this->lazyProperty1 === null) { + $this->lazyProperty1 = 1; + } + + return $this->lazyProperty1; + case "property2": + if ($this->lazyProperty2 === null) { + $this->lazyProperty2 = "foo"; + } + + return $this->lazyProperty2; + case "property3": + if ($this->lazyProperty3 === null) { + $this->lazyProperty3 = new stdClass(); + } + + return $this->lazyProperty3; + default: + throw new Exception(); + } + } +} + +$foo = new Bar(); + +var_dump($foo->property1); +var_dump($foo->property2); +var_dump($foo->property3); + +?> +--EXPECT-- +int(1) +string(3) "foo" +object(stdClass)#2 (0) { +} diff --git a/Zend/tests/final_properties/access/property_array_access.phpt b/Zend/tests/final_properties/access/property_array_access.phpt new file mode 100644 index 0000000000000..d312819ab0896 --- /dev/null +++ b/Zend/tests/final_properties/access/property_array_access.phpt @@ -0,0 +1,52 @@ +--TEST-- +Test that ArrayAccess works for final properties as expected +--FILE-- +$offset); + } + + function offsetGet($offset) + { + return $this->$offset ?? null; + } + + function offsetSet($offset, $value) + { + $this->$offset = $value; + } + + function offsetUnset($offset){ + unset($this->$offset); + } +} + +$foo = new Foo(); + +$foo["property1"] = "foo"; + +try { + $foo["property1"] = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo["property2"] = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$foo["property3"] = "foo"; + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization diff --git a/Zend/tests/final_properties/access/property_array_functions.phpt b/Zend/tests/final_properties/access/property_array_functions.phpt new file mode 100644 index 0000000000000..8cb116a8830ed --- /dev/null +++ b/Zend/tests/final_properties/access/property_array_functions.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test that final properties can be used with key() and current(), but not with next() +--XFAIL-- +Incorrect error message +--FILE-- +property1)); +var_dump(key($foo->property1)); + +try { + next($foo->property1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +string(3) "foo" +int(0) +Cannot modify final property Foo::$property1 after initialization diff --git a/Zend/tests/final_properties/access/property_assignment1.phpt b/Zend/tests/final_properties/access/property_assignment1.phpt new file mode 100644 index 0000000000000..3758b5747cc42 --- /dev/null +++ b/Zend/tests/final_properties/access/property_assignment1.phpt @@ -0,0 +1,104 @@ +--TEST-- +Test that final properties can't be mutated by assignment operators +--FILE-- +property1 = $foo->property1 + 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 - 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 * 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 / 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 ** 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 & 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 | 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 ^ 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 << 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 = $foo->property1 >> 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2 = $foo->property2 . "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2 = $foo->property2 ?? "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->property1); +var_dump($foo->property2); + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization +Cannot modify final property Foo::$property2 after initialization +int(1) +string(0) "" diff --git a/Zend/tests/final_properties/access/property_assignment2.phpt b/Zend/tests/final_properties/access/property_assignment2.phpt new file mode 100644 index 0000000000000..ee3aef9d32cfa --- /dev/null +++ b/Zend/tests/final_properties/access/property_assignment2.phpt @@ -0,0 +1,104 @@ +--TEST-- +Test that final static properties can't be mutated by assignment operators +--FILE-- +getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 - 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 * 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 / 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 ** 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 & 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 | 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 ^ 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 << 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 = Foo::$property1 >> 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property2 = Foo::$property2 . "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property2 = Foo::$property2 ?? "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump( Foo::$property1); +var_dump( Foo::$property2); + +?> +--EXPECT-- +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property2 after initialization +Cannot modify final static property Foo::$property2 after initialization +int(1) +string(0) "" diff --git a/Zend/tests/final_properties/access/property_assignment_combined1.phpt b/Zend/tests/final_properties/access/property_assignment_combined1.phpt new file mode 100644 index 0000000000000..43625aee0981e --- /dev/null +++ b/Zend/tests/final_properties/access/property_assignment_combined1.phpt @@ -0,0 +1,107 @@ +--TEST-- +Test that final properties can't be mutated by combined assignment operators +--FILE-- +property1 += 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 -= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 *= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 /= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 **= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 &= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 |= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 ^= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 <<= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1 >>= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +// $property2 must be initialized to null, otherwise the subsequent ??= would do nothing +$foo->property2 ??= null; + +try { + $foo->property2 ??= "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2 .= "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->property1); +var_dump($foo->property2); + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization +Cannot modify final property Foo::$property2 after initialization +int(1) +NULL diff --git a/Zend/tests/final_properties/access/property_assignment_combined2.phpt b/Zend/tests/final_properties/access/property_assignment_combined2.phpt new file mode 100644 index 0000000000000..e1d1fde383a92 --- /dev/null +++ b/Zend/tests/final_properties/access/property_assignment_combined2.phpt @@ -0,0 +1,107 @@ +--TEST-- +Test that final static properties can't be mutated by combined assignment operators +--FILE-- +getMessage() . "\n"; +} + +try { + Foo::$property1 -= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 *= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 /= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 **= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 &= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 |= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 ^= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 <<= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property1 >>= 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +// $property2 must be initialized to null, otherwise the subsequent ??= would do nothing +Foo::$property2 ??= null; + +try { + Foo::$property2 ??= "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property2 .= "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump(Foo::$property1); +var_dump(Foo::$property2); + +?> +--EXPECT-- +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property2 after initialization +Cannot modify final static property Foo::$property2 after initialization +int(1) +NULL diff --git a/Zend/tests/final_properties/access/property_by_ref_access1.phpt b/Zend/tests/final_properties/access/property_by_ref_access1.phpt new file mode 100644 index 0000000000000..38a6123a91a93 --- /dev/null +++ b/Zend/tests/final_properties/access/property_by_ref_access1.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test that final properties can't be accessed by reference +--FILE-- +property1 = 1; + } + + public function &getProperty2(): int + { + return $this->property2; + } +} + +$foo = new Foo(); + +try { + $x = &$foo->property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->getProperty2(); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to final property Foo::$property1 +Cannot acquire reference to final property Foo::$property2 diff --git a/Zend/tests/final_properties/access/property_by_ref_access2.phpt b/Zend/tests/final_properties/access/property_by_ref_access2.phpt new file mode 100644 index 0000000000000..0bc92aeed78cf --- /dev/null +++ b/Zend/tests/final_properties/access/property_by_ref_access2.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test that final properties can't be accessed by reference +--FILE-- +property1 = 1; + } +} + +$foo = new Foo(); + +try { + foreach ($foo as &$property) {} +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to final property Foo::$property1 diff --git a/Zend/tests/final_properties/access/property_incrementing_decrementing.phpt b/Zend/tests/final_properties/access/property_incrementing_decrementing.phpt new file mode 100644 index 0000000000000..e790384d6a75e --- /dev/null +++ b/Zend/tests/final_properties/access/property_incrementing_decrementing.phpt @@ -0,0 +1,98 @@ +--TEST-- +Test that final properties can't be incremented/decremented +--FILE-- +property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1++; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$foo->property1 = 1; +$foo->property2 = 2; + +try { + ++$foo->property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + ++$foo->property2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1++; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2++; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->property1); +var_dump($foo->property2); + +try { + --$foo->property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + --$foo->property2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property1--; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2--; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->property1); +var_dump($foo->property2); + +?> +--EXPECT-- +Typed property Foo::$property1 must not be accessed before initialization +Typed property Foo::$property1 must not be accessed before initialization +Cannot increment final property Foo::$property1 +Cannot increment final property Foo::$property2 +Cannot increment final property Foo::$property1 +Cannot increment final property Foo::$property2 +int(1) +int(2) +Cannot decrement final property Foo::$property1 +Cannot decrement final property Foo::$property2 +Cannot decrement final property Foo::$property1 +Cannot decrement final property Foo::$property2 +int(1) +int(2) diff --git a/Zend/tests/final_properties/access/property_iteration.phpt b/Zend/tests/final_properties/access/property_iteration.phpt new file mode 100644 index 0000000000000..d5852f37f6b97 --- /dev/null +++ b/Zend/tests/final_properties/access/property_iteration.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test that final array properties can be iterated +--FILE-- +property1 as $key => $value) { + var_dump("$key => $value"); +} + +?> +--EXPECT-- +string(8) "0 => foo" +string(8) "1 => bar" +string(8) "2 => baz" diff --git a/Zend/tests/final_properties/access/property_magic_get.phpt b/Zend/tests/final_properties/access/property_magic_get.phpt new file mode 100644 index 0000000000000..5fb75fdb4c10c --- /dev/null +++ b/Zend/tests/final_properties/access/property_magic_get.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test that the __get() magic method works when reading from a final property +--FILE-- + "foo", + "property2" => null, + ]; + + public function __isset($name): bool + { + return isset($this->properties[$name]); + } + + public function __get($name) + { + return $this->properties[$name] ?? null; + } +} + +$foo = new Foo(); + +var_dump(isset($foo->property1)); +var_dump($foo->property1); + +var_dump(isset($foo->property2)); +var_dump($foo->property2); + +var_dump(isset($foo->property3)); +var_dump($foo->property3); + +?> +--EXPECT-- +bool(true) +string(3) "foo" +bool(false) +NULL +bool(false) +NULL diff --git a/Zend/tests/final_properties/access/property_magic_set1.phpt b/Zend/tests/final_properties/access/property_magic_set1.phpt new file mode 100644 index 0000000000000..de9b0166b632e --- /dev/null +++ b/Zend/tests/final_properties/access/property_magic_set1.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test that the __set() magic method works as expected when a final property is modified +--FILE-- + "foo", + "property2" => null, + "property3" => [], + ]; + + public function __set($name, $value): void + { + $this->properties[$name] = $value; + } +} + +$foo = new Foo(); + +try { + $foo->property1 = "bar"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2 = "baz"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property3 = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property4 = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->properties); + +?> +--EXPECT-- +Cannot modify final property Foo::$properties after initialization +Cannot modify final property Foo::$properties after initialization +Cannot modify final property Foo::$properties after initialization +Cannot modify final property Foo::$properties after initialization +array(3) { + ["property1"]=> + string(3) "foo" + ["property2"]=> + NULL + ["property3"]=> + array(0) { + } +} diff --git a/Zend/tests/final_properties/access/property_magic_set2.phpt b/Zend/tests/final_properties/access/property_magic_set2.phpt new file mode 100644 index 0000000000000..838435ddfaac2 --- /dev/null +++ b/Zend/tests/final_properties/access/property_magic_set2.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test that the __set() magic method works as expected for final properties +--FILE-- +$name = $value; + } +} + +$foo = new Foo(); + +try { + $foo->property1 = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$foo->property2 = "foo"; + +try { + $foo->property2 = "foo"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization diff --git a/Zend/tests/final_properties/access/property_mutation.phpt b/Zend/tests/final_properties/access/property_mutation.phpt new file mode 100644 index 0000000000000..5be29eee94681 --- /dev/null +++ b/Zend/tests/final_properties/access/property_mutation.phpt @@ -0,0 +1,99 @@ +--TEST-- +Test that final properties can't be mutated +--FILE-- +property1 = 1; + $this->property4 = new stdClass(); + } + + public function getProperty1(): int + { + return $this->property1; + } + + public function withProperty1(int $property): Foo + { + $this->property1 = $property; + + return $this; + } + + public function getProperty2(): string + { + return $this->property2; + } + + public function withProperty2(string $property): Foo + { + $this->property2 = $property; + + return $this; + } +} + +$foo = new Foo(); + +try { + $foo = $foo->withProperty1(1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo = $foo->withProperty2("Bar"); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$foo->property3[] = 1; + +try { + $foo->property3[] = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property3["foo"] = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset($foo->property3[0]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset($foo->property3["foo"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->property3); + +$foo->property4->foo = "foo"; + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization +Cannot modify final property Foo::$property3 after initialization +Cannot modify final property Foo::$property3 after initialization +Cannot modify final property Foo::$property3 after initialization +Cannot modify final property Foo::$property3 after initialization +array(1) { + [0]=> + int(1) +} diff --git a/Zend/tests/final_properties/access/property_mutation_by_ref.phpt b/Zend/tests/final_properties/access/property_mutation_by_ref.phpt new file mode 100644 index 0000000000000..879ab047cb21a --- /dev/null +++ b/Zend/tests/final_properties/access/property_mutation_by_ref.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test that final properties can't be mutated by reference +--FILE-- +property1 = &$x; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$foo->property1 = 1; + +try { + $foo->property1 = &$x; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $foo->property2 = &$foo->property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot assign to final property Foo::$property1 by reference +Cannot assign to final property Foo::$property1 by reference +Cannot acquire reference to final property Foo::$property1 diff --git a/Zend/tests/final_properties/access/property_mutation_static.phpt b/Zend/tests/final_properties/access/property_mutation_static.phpt new file mode 100644 index 0000000000000..9572048b83e90 --- /dev/null +++ b/Zend/tests/final_properties/access/property_mutation_static.phpt @@ -0,0 +1,77 @@ +--TEST-- +Test that final static properties can't be mutated +--XFAIL-- +Object properties are fetched with incorrect access mode +--FILE-- +getMessage() . "\n"; +} + +try { + Foo::$property2 = "bar"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +Foo::$property3[] = 1; + +try { + Foo::$property3[] = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::$property3["foo"] = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset(Foo::$property3[0]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset(Foo::$property3["foo"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump(Foo::$property3); + +Foo::$property4->foo = "foo"; + +?> +--EXPECT-- +Cannot modify final static property Foo::$property1 after initialization +Cannot modify final static property Foo::$property2 after initialization +Cannot modify final static property Foo::$property3 after initialization +Cannot modify final static property Foo::$property3 after initialization +Cannot modify final static property Foo::$property3 after initialization +Cannot modify final static property Foo::$property3 after initialization +array(1) { + [0]=> + int(1) +} diff --git a/Zend/tests/final_properties/access/property_pass_by_ref1.phpt b/Zend/tests/final_properties/access/property_pass_by_ref1.phpt new file mode 100644 index 0000000000000..cb7d837a20ad7 --- /dev/null +++ b/Zend/tests/final_properties/access/property_pass_by_ref1.phpt @@ -0,0 +1,47 @@ +--TEST-- +Test that final properties can't be passed by reference +--XFAIL-- +Incorrect error message in case of static properties ("Cannot modify final static property ...") +--FILE-- +property1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + sort($foo->property2); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + preg_match("/a/", "a", Foo::$property3); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + sort(Foo::$property4); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to final property Foo::$property1 +Cannot acquire reference to final property Foo::$property1 +Cannot acquire reference to final property Foo::$property2 +Cannot acquire reference to final property Foo::$property2 diff --git a/Zend/tests/final_properties/access/property_pass_by_ref2.phpt b/Zend/tests/final_properties/access/property_pass_by_ref2.phpt new file mode 100644 index 0000000000000..ed6289684fbdf --- /dev/null +++ b/Zend/tests/final_properties/access/property_pass_by_ref2.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test that final properties can't be passed by reference +--FILE-- + "abc"]; + final public static array $property2 = ["field" => "abc"]; +} + +$foo = new Foo(); + +try { + preg_match("/a/", "b", $foo->property1["field"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + sort($foo->property1["field"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + preg_match("/a/", "a", Foo::$property2["field"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + sort(Foo::$property2["field"]); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property1 after initialization +Cannot modify final static property Foo::$property2 after initialization +Cannot modify final static property Foo::$property2 after initialization diff --git a/Zend/tests/final_properties/access/property_read1.phpt b/Zend/tests/final_properties/access/property_read1.phpt new file mode 100644 index 0000000000000..3654b674fa680 --- /dev/null +++ b/Zend/tests/final_properties/access/property_read1.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test that final properties of scalar types can be accessed for reading +--FILE-- +property1); +var_dump($foo->property2); +var_dump($foo->property3); + +var_dump(Foo::$property4); +var_dump(Foo::$property5); +var_dump(Foo::$property6); + +?> +--EXPECT-- +bool(false) +int(2) +string(3) "Bar" +bool(false) +int(5) +string(3) "Baz" diff --git a/Zend/tests/final_properties/access/property_read2.phpt b/Zend/tests/final_properties/access/property_read2.phpt new file mode 100644 index 0000000000000..d60c98edda7f4 --- /dev/null +++ b/Zend/tests/final_properties/access/property_read2.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test that final properties of composite types can be accessed for reading +--XFAIL-- +Object properties are fetched with incorrect access mode +--FILE-- +property2 = new stdClass(); +Foo::$property4 = new stdClass(); + +echo "Object properties:\n"; +var_dump($foo->property1); +var_dump($foo->property1[0]); + +var_dump($foo->property2); +$foo->property2->foo = "foo"; +var_dump($foo->property2->foo); +var_dump($foo->property2->bar); + +echo "\nClass properties:\n"; +var_dump(Foo::$property3); +var_dump(Foo::$property3[0]); + +var_dump(Foo::$property4); +Foo::$property4->foo = "foo"; +var_dump(Foo::$property4->foo); +var_dump(Foo::$property4->bar); + +?> +--EXPECTF-- +Object properties: +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" +} +string(3) "foo" +object(stdClass)#2 (0) { +} +string(3) "foo" + +Warning: Undefined property: stdClass::$bar in %s on line %d +NULL + +Class properties: +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" +} +string(3) "foo" +object(stdClass)#3 (0) { +} +string(3) "foo" + +Warning: Undefined property: stdClass::$bar in %s on line %d +NULL diff --git a/Zend/tests/final_properties/access/property_read_by_ref1.phpt b/Zend/tests/final_properties/access/property_read_by_ref1.phpt new file mode 100644 index 0000000000000..a399b3f9e7c69 --- /dev/null +++ b/Zend/tests/final_properties/access/property_read_by_ref1.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test that final properties of scalar types can't be accessed by reference +--XFAIL-- +Incorrect error message in case of static properties ("Cannot modify final static property ...") +--FILE-- +property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &$foo->property2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &$foo->property3; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &Foo::$property4; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &Foo::$property5; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &Foo::$property6; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to final property Foo::$property1 +Cannot acquire reference to final property Foo::$property2 +Cannot acquire reference to final property Foo::$property3 +Cannot acquire reference to final property Foo::$property4 +Cannot acquire reference to final property Foo::$property5 +Cannot acquire reference to final property Foo::$property6 diff --git a/Zend/tests/final_properties/access/property_read_by_ref2.phpt b/Zend/tests/final_properties/access/property_read_by_ref2.phpt new file mode 100644 index 0000000000000..4d81fb4c34b6b --- /dev/null +++ b/Zend/tests/final_properties/access/property_read_by_ref2.phpt @@ -0,0 +1,52 @@ +--TEST-- +Test that final properties of composite type can't be accessed by reference +--XFAIL-- +Incorrect error message in case of static properties ("Cannot modify final static property ...") +--FILE-- +property2 = new stdClass(); +Foo::$property4 = new stdClass(); + +$var = null; + +try { + $var = &$foo->property1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &$foo->property2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &Foo::$property3; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &Foo::$property4; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot acquire reference to final property Foo::$property1 +Cannot acquire reference to final property Foo::$property2 +Cannot acquire reference to final property Foo::$property3 +Cannot acquire reference to final property Foo::$property4 diff --git a/Zend/tests/final_properties/clone/clone_error.phpt b/Zend/tests/final_properties/clone/clone_error.phpt new file mode 100644 index 0000000000000..c498f9102b0c0 --- /dev/null +++ b/Zend/tests/final_properties/clone/clone_error.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test that initialized final properties can't be modified after cloning +--FILE-- +property1 = 1; +$bar = clone $foo; + +try { + $bar->property1 = 2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $bar->property2 = "Baz"; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + $var = &$foo->property2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify final property Foo::$property1 after initialization +Cannot modify final property Foo::$property2 after initialization +Cannot acquire reference to final property Foo::$property2 diff --git a/Zend/tests/final_properties/clone/clone_success1.phpt b/Zend/tests/final_properties/clone/clone_success1.phpt new file mode 100644 index 0000000000000..a38d81fdfb3eb --- /dev/null +++ b/Zend/tests/final_properties/clone/clone_success1.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test that final properties can be cloned +--FILE-- +property2 = 2; +var_dump($foo); +$bar = clone $foo; +var_dump($bar); + +?> +--EXPECTF-- +object(Foo)#1 (2) { + ["property1"]=> + uninitialized(int) + ["property2"]=> + int(2) + ["property3"]=> + string(0) "" +} +object(Foo)#2 (2) { + ["property1"]=> + uninitialized(int) + ["property2"]=> + int(2) + ["property3"]=> + string(0) "" +} diff --git a/Zend/tests/final_properties/clone/clone_success2.phpt b/Zend/tests/final_properties/clone/clone_success2.phpt new file mode 100644 index 0000000000000..e9123855cbb92 --- /dev/null +++ b/Zend/tests/final_properties/clone/clone_success2.phpt @@ -0,0 +1,26 @@ +--TEST-- +Test that uninitialized final properties can be initialized after cloning +--FILE-- +property1 = 1; +var_dump($bar); + +?> +--EXPECTF-- +object(Foo)#1 (0) { + ["property1"]=> + uninitialized(int) +} +object(Foo)#2 (1) { + ["property1"]=> + int(1) +} diff --git a/Zend/tests/final_properties/inheritance/inheritance_error.phpt b/Zend/tests/final_properties/inheritance/inheritance_error.phpt new file mode 100644 index 0000000000000..a618090fbd5b2 --- /dev/null +++ b/Zend/tests/final_properties/inheritance/inheritance_error.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test that non-private final properties can't override non-final properties +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare non-final property Foo::$property1 as final Bar::$property1 in %s on line %d diff --git a/Zend/tests/final_properties/inheritance/inheritance_error2.phpt b/Zend/tests/final_properties/inheritance/inheritance_error2.phpt new file mode 100644 index 0000000000000..1e01ef09e9955 --- /dev/null +++ b/Zend/tests/final_properties/inheritance/inheritance_error2.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test that non-private final static properties can't override non-final static properties +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare non-final property Foo::$property1 as final Bar::$property1 in %s on line %d diff --git a/Zend/tests/final_properties/inheritance/inheritance_success1.phpt b/Zend/tests/final_properties/inheritance/inheritance_success1.phpt new file mode 100644 index 0000000000000..31fa851ba915a --- /dev/null +++ b/Zend/tests/final_properties/inheritance/inheritance_success1.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test that non-final properties can override final properties +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/final_properties/inheritance/inheritance_success2.phpt b/Zend/tests/final_properties/inheritance/inheritance_success2.phpt new file mode 100644 index 0000000000000..eeb9ea1d86b2e --- /dev/null +++ b/Zend/tests/final_properties/inheritance/inheritance_success2.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test that private final properties can override non-final properties +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/final_properties/serialization/unserialize_error1.phpt b/Zend/tests/final_properties/serialization/unserialize_error1.phpt new file mode 100644 index 0000000000000..398d23fd56824 --- /dev/null +++ b/Zend/tests/final_properties/serialization/unserialize_error1.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test that final properties can only be assigned to once during unserialization +--XFAIL-- +Doesn't work yet. +--FILE-- + +--EXPECT-- +Cannot modify final property Foo::$finalProp after initialization diff --git a/Zend/tests/final_properties/serialization/unserialize_error2.phpt b/Zend/tests/final_properties/serialization/unserialize_error2.phpt new file mode 100644 index 0000000000000..192df974cf436 --- /dev/null +++ b/Zend/tests/final_properties/serialization/unserialize_error2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test that final properties cannot be modified even when they were serialized as non-final +--XFAIL-- +Doesn't work yet +--FILE-- +refProp = 4; +var_dump($foo->finalProp); + +?> +--EXPECT-- +int(2) diff --git a/Zend/tests/final_properties/serialization/unserialize_success1.phpt b/Zend/tests/final_properties/serialization/unserialize_success1.phpt new file mode 100644 index 0000000000000..6f828a470bf43 --- /dev/null +++ b/Zend/tests/final_properties/serialization/unserialize_success1.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test that final properties can be serialized and then unserialized +--FILE-- + +--EXPECT-- +object(Foo)#2 (1) { + ["property1"]=> + uninitialized(int) + ["property2"]=> + string(0) "" +} diff --git a/Zend/tests/final_properties/serialization/unserialize_success2.phpt b/Zend/tests/final_properties/serialization/unserialize_success2.phpt new file mode 100644 index 0000000000000..51a304d41a8d4 --- /dev/null +++ b/Zend/tests/final_properties/serialization/unserialize_success2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test that final properties can be unserialized even when they were serialized as non-final +--FILE-- + +--EXPECT-- +object(MyClass)#1 (2) { + ["finalProp"]=> + &int(2) + ["refProp"]=> + &int(2) +} diff --git a/Zend/tests/final_properties/syntax/property_final.phpt b/Zend/tests/final_properties/syntax/property_final.phpt new file mode 100644 index 0000000000000..301228fb526b3 --- /dev/null +++ b/Zend/tests/final_properties/syntax/property_final.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test valid syntax of final properties +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/final_properties/syntax/property_final_invalid1.phpt b/Zend/tests/final_properties/syntax/property_final_invalid1.phpt new file mode 100644 index 0000000000000..ca04815df126e --- /dev/null +++ b/Zend/tests/final_properties/syntax/property_final_invalid1.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test invalid syntax of final properties +--FILE-- + +--EXPECTF-- +Fatal error: Multiple final modifiers are not allowed in %S on line %d diff --git a/Zend/tests/final_properties/syntax/property_final_invalid2.phpt b/Zend/tests/final_properties/syntax/property_final_invalid2.phpt new file mode 100644 index 0000000000000..1773e38c154ca --- /dev/null +++ b/Zend/tests/final_properties/syntax/property_final_invalid2.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test invalid syntax of final constants +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use 'final' as constant modifier in %s on line %d diff --git a/Zend/tests/final_properties/unset/declared_unset_error.phpt b/Zend/tests/final_properties/unset/declared_unset_error.phpt new file mode 100644 index 0000000000000..54c29a906c745 --- /dev/null +++ b/Zend/tests/final_properties/unset/declared_unset_error.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test that final properties can't be unset +--FILE-- +property3); + } +} + +$foo = new Foo(); + +try { + unset($foo->property1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +unset($foo->property2); +unset($foo->property2); + +try { + $foo->unsetProperty3(); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot unset final property Foo::$property1 +Cannot unset final property Foo::$property3 diff --git a/Zend/tests/final_properties/unset/magic_unset_error1.phpt b/Zend/tests/final_properties/unset/magic_unset_error1.phpt new file mode 100644 index 0000000000000..2ff6eabcbc2c0 --- /dev/null +++ b/Zend/tests/final_properties/unset/magic_unset_error1.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test that the __unset() magic method results in an exception +--FILE-- +$name); + } + + public function __get($name) + { + return $this->$name ?? null; + } + + public function __set($name, $value): void + { + $this->$name = $value; + } + + public function __unset($name): void + { + unset($this->$name); + } +} + +$foo = new Foo(); + +try { + unset($foo->property1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +unset($foo->property2); + +?> +--EXPECT-- +Cannot unset final property Foo::$property1 diff --git a/Zend/tests/final_properties/unset/magic_unset_error2.phpt b/Zend/tests/final_properties/unset/magic_unset_error2.phpt new file mode 100644 index 0000000000000..f37f7e750e4fa --- /dev/null +++ b/Zend/tests/final_properties/unset/magic_unset_error2.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test that the __unset() magic method results in an exception +--FILE-- + null, + ]; + + public function __isset($name): bool + { + return isset($this->properties[$name]); + } + + public function __get($name) + { + return $this->properties[$name] ?? null; + } + + public function __set($name, $value): void + { + $this->properties[$name] = $value; + } + + public function __unset($name): void + { + unset($this->properties[$name]); + } +} + +$foo = new Foo(); + +try { + unset($foo->property1); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset($foo->property2); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + unset($foo->properties); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +var_dump($foo->properties); + +?> +--EXPECT-- +Cannot modify final property Foo::$properties after initialization +Cannot modify final property Foo::$properties after initialization +Cannot unset final property Foo::$properties +array(1) { + ["property1"]=> + NULL +} diff --git a/Zend/tests/final_properties/unset/magic_unset_success.phpt b/Zend/tests/final_properties/unset/magic_unset_success.phpt new file mode 100644 index 0000000000000..e6a9428d516c7 --- /dev/null +++ b/Zend/tests/final_properties/unset/magic_unset_success.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test that the __unset() magic method is successful +--FILE-- +properties[$name]); + } + + public function __get($name) + { + return $this->properties[$name] ?? null; + } + + public function __set($name, $value): void + { + $this->properties[$name] = $value; + } + + public function __unset($name): void + { + unset($this->properties); + } +} + +$foo = new Foo(); + +unset($foo->property1); +var_dump($foo->properties); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/final_properties/unset/static_declared_unset_error.phpt b/Zend/tests/final_properties/unset/static_declared_unset_error.phpt new file mode 100644 index 0000000000000..377196d501000 --- /dev/null +++ b/Zend/tests/final_properties/unset/static_declared_unset_error.phpt @@ -0,0 +1,54 @@ +--TEST-- +Test that unsetting declared static final properties is not possible +--FILE-- +getMessage() . "\n"; +} + +try { + unset(Foo::$property2); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::unsetProperty3(); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + Foo::unsetProperty4(); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Attempt to unset static property Foo::$property1 +Attempt to unset static property Foo::$property2 +Attempt to unset static property Foo::$property3 +Attempt to unset static property Foo::$property4 diff --git a/Zend/zend_API.c b/Zend/zend_API.c index dd823d627ce41..5d71697e4ae4d 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3569,7 +3569,7 @@ ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name case IS_ARRAY: case IS_OBJECT: case IS_RESOURCE: - zend_error_noreturn(E_CORE_ERROR, "Internal zval's can't be arrays, objects or resources"); + zend_error_noreturn(E_CORE_ERROR, "Internal ZVALs can't be arrays, objects or resources"); break; default: break; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7cfc0450fd84e..304e5efdd9715 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1524,6 +1524,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } else if (ast->attr & ZEND_ACC_PRIVATE) { smart_str_appends(str, "private "); } + if (ast->attr & ZEND_ACC_FINAL) { + smart_str_appends(str, "final "); + } if (ast->attr & ZEND_ACC_STATIC) { smart_str_appends(str, "static "); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2050e3ba71151..0510c23e3b6a5 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2653,6 +2653,8 @@ static zend_op *zend_delayed_compile_dim(znode *result, zend_ast *ast, uint32_t opline = zend_delayed_compile_var(&var_node, var_ast, type, 0); if (opline && type == BP_VAR_W && (opline->opcode == ZEND_FETCH_STATIC_PROP_W || opline->opcode == ZEND_FETCH_OBJ_W)) { opline->extended_value |= ZEND_FETCH_DIM_WRITE; + } else if (opline && type == BP_VAR_UNSET && (opline->opcode == ZEND_FETCH_STATIC_PROP_UNSET || opline->opcode == ZEND_FETCH_OBJ_UNSET)) { + /*opline->extended_value |= ZEND_FETCH_DIM_UNSET_FLAG;*/ } zend_separate_if_call_and_write(&var_node, var_ast, type); @@ -6396,6 +6398,11 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / "Property %s::$%s cannot have type %s", ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(str)); } + } else if (flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, + "Final property %s::$%s must have a type", + ZSTR_VAL(ce->name), ZSTR_VAL(name) + ); } /* Doc comment has been appended as last element in ZEND_AST_PROP_ELEM ast */ @@ -6403,12 +6410,6 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / doc_comment = zend_string_copy(zend_ast_get_str(doc_comment_ast)); } - if (flags & ZEND_ACC_FINAL) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare property %s::$%s final, " - "the final modifier is allowed only for methods and classes", - ZSTR_VAL(ce->name), ZSTR_VAL(name)); - } - if (zend_hash_exists(&ce->properties_info, name)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(name)); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8a64ed86c3d03..e37ca30d4bc30 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -209,7 +209,7 @@ typedef struct _zend_oparray_context { /* Static method or property | | | */ #define ZEND_ACC_STATIC (1 << 4) /* | X | X | */ /* | | | */ -/* Final class or method | | | */ +/* Final class, property, or method | | | */ #define ZEND_ACC_FINAL (1 << 5) /* X | X | | */ /* | | | */ /* Abstract method | | | */ @@ -907,9 +907,10 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FETCH_TYPE_MASK 0xe /* Only one of these can ever be in use */ -#define ZEND_FETCH_REF 1 -#define ZEND_FETCH_DIM_WRITE 2 -#define ZEND_FETCH_OBJ_FLAGS 3 +#define ZEND_FETCH_REF 1 +#define ZEND_FETCH_DIM_WRITE 2 +#define ZEND_FETCH_DIM_UNSET_FLAG 3 +#define ZEND_FETCH_OBJ_FLAGS 3 #define ZEND_ISEMPTY (1<<0) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6f6fb59ecab88..02707a6b2aa10 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -555,6 +555,13 @@ static inline void zend_assign_to_variable_reference(zval *variable_ptr, zval *v static zend_never_inline zval* zend_assign_to_typed_property_reference(zend_property_info *prop_info, zval *prop, zval *value_ptr EXECUTE_DATA_DC) { + if (prop_info && prop_info->flags & ZEND_ACC_FINAL) { + zend_throw_error(NULL, "Cannot assign to final property %s::$%s by reference", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + return &EG(uninitialized_zval); + } + if (!zend_verify_prop_assignable_by_ref(prop_info, value_ptr, EX_USES_STRICT_TYPES())) { return &EG(uninitialized_zval); } @@ -640,6 +647,11 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro } } +static zend_never_inline ZEND_COLD void zend_final_property_assignment_error(zend_class_entry *ce, const char *property_name) +{ + zend_throw_error(NULL, "Cannot modify final property %s::$%s after initialization", ZSTR_VAL(ce->name), property_name); +} + static ZEND_COLD void zend_verify_type_error_common( const zend_function *zf, const zend_arg_info *arg_info, void **cache_slot, zval *value, @@ -946,6 +958,12 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf ZVAL_DEREF(value); ZVAL_COPY(&tmp, value); + if (UNEXPECTED(info->flags & ZEND_ACC_FINAL && Z_PROP_FLAG_P(&tmp) != IS_PROP_UNINIT)) { + zend_final_property_assignment_error(info->ce, zend_get_unmangled_property_name(info->name)); + zval_ptr_dtor(&tmp); + return &EG(uninitialized_zval); + } + if (UNEXPECTED(!i_zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(&tmp); return &EG(uninitialized_zval); @@ -1338,6 +1356,11 @@ static zend_never_inline void zend_binary_assign_op_typed_prop(zend_property_inf { zval z_copy; + if (UNEXPECTED(prop_info->flags & ZEND_ACC_FINAL)) { + zend_final_property_assignment_error(prop_info->ce, zend_get_unmangled_property_name(prop_info->name)); + return; + } + zend_binary_op(&z_copy, zptr, value OPLINE_CC); if (EXPECTED(zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(zptr); @@ -1637,6 +1660,14 @@ static ZEND_COLD zend_long zend_throw_incdec_prop_error(zend_property_info *prop } } +static ZEND_COLD void zend_throw_final_property_incdec_error(zend_property_info *prop OPLINE_DC) { + if (ZEND_IS_INCREMENT(opline->opcode)) { + zend_throw_error(NULL, "Cannot increment final property %s::$%s", ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name)); + } else { + zend_throw_error(NULL, "Cannot decrement final property %s::$%s", ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name)); + } +} + static void zend_incdec_typed_ref(zend_reference *ref, zval *copy OPLINE_DC EXECUTE_DATA_DC) { zval tmp; @@ -1673,6 +1704,11 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr, { zval tmp; + if (UNEXPECTED(prop_info->flags & ZEND_ACC_FINAL)) { + zend_throw_final_property_incdec_error(prop_info OPLINE_CC); + return; + } + if (!copy) { copy = &tmp; } @@ -1702,11 +1738,17 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr, static void zend_pre_incdec_property_zval(zval *prop, zend_property_info *prop_info OPLINE_DC EXECUTE_DATA_DC) { if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { + if (UNEXPECTED(prop_info && prop_info->flags & ZEND_ACC_FINAL)) { + zend_throw_final_property_incdec_error(prop_info OPLINE_CC); + return; + } + if (ZEND_IS_INCREMENT(opline->opcode)) { fast_long_increment_function(prop); } else { fast_long_decrement_function(prop); } + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); @@ -1741,11 +1783,18 @@ static void zend_post_incdec_property_zval(zval *prop, zend_property_info *prop_ { if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) { ZVAL_LONG(EX_VAR(opline->result.var), Z_LVAL_P(prop)); + + if (UNEXPECTED(prop_info && prop_info->flags & ZEND_ACC_FINAL)) { + zend_throw_final_property_incdec_error(prop_info OPLINE_CC); + return; + } + if (ZEND_IS_INCREMENT(opline->opcode)) { fast_long_increment_function(prop); } else { fast_long_decrement_function(prop); } + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); @@ -1781,7 +1830,7 @@ static zend_never_inline void zend_post_incdec_overloaded_property(zend_object * zval z_copy; GC_ADDREF(object); - z =object->handlers->read_property(object, name, BP_VAR_R, cache_slot, &rv); + z = object->handlers->read_property(object, name, BP_VAR_R, cache_slot, &rv); if (UNEXPECTED(EG(exception))) { OBJ_RELEASE(object); ZVAL_UNDEF(EX_VAR(opline->result.var)); @@ -2614,11 +2663,35 @@ static zend_never_inline zend_bool zend_handle_fetch_obj_flags( break; } } + + if (prop_info->flags & ZEND_ACC_FINAL && Z_PROP_FLAG_P(ptr) != IS_PROP_UNINIT) { + zend_throw_error(NULL, "Cannot modify final property %s::$%s after initialization", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + if (result) ZVAL_ERROR(result); + return 0; + } + if (!check_type_array_assignable(prop_info->type)) { zend_throw_auto_init_in_prop_error(prop_info, "array"); if (result) ZVAL_ERROR(result); return 0; } + } else if (Z_TYPE_P(ptr) == IS_ARRAY) { + if (!prop_info) { + prop_info = zend_object_fetch_property_type_info(obj, ptr); + if (!prop_info) { + break; + } + } + + if (prop_info->flags & ZEND_ACC_FINAL) { + zend_throw_error(NULL, "Cannot modify final property %s::$%s after initialization", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + if (result) ZVAL_ERROR(result); + return 0; + } } break; case ZEND_FETCH_REF: @@ -2629,6 +2702,15 @@ static zend_never_inline zend_bool zend_handle_fetch_obj_flags( break; } } + + if (prop_info->flags & ZEND_ACC_FINAL) { + zend_throw_error(NULL, "Cannot acquire reference to final property %s::$%s", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + if (result) ZVAL_ERROR(result); + return 0; + } + if (Z_TYPE_P(ptr) == IS_UNDEF) { if (!ZEND_TYPE_ALLOW_NULL(prop_info->type)) { zend_throw_access_uninit_prop_by_ref_error(prop_info); @@ -2642,6 +2724,22 @@ static zend_never_inline zend_bool zend_handle_fetch_obj_flags( ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(ptr), prop_info); } break; + case ZEND_FETCH_DIM_UNSET_FLAG: + if (!prop_info) { + prop_info = zend_object_fetch_property_type_info(obj, ptr); + if (!prop_info) { + break; + } + } + + if (prop_info->flags & ZEND_ACC_FINAL && Z_PROP_FLAG_P(ptr) != IS_PROP_UNINIT) { + zend_throw_error(NULL, "Cannot modify final property %s::$%s after initialization", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + if (result) ZVAL_ERROR(result); + return 0; + } + break; EMPTY_SWITCH_DEFAULT_CASE() } return 1; @@ -3136,10 +3234,12 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *value, zend_uc ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_property_info *prop_info, zval *orig_val, zend_bool strict) { zval *val = orig_val; + if (Z_ISREF_P(val) && ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(val))) { int result; val = Z_REFVAL_P(val); + result = i_zend_verify_type_assignable_zval(prop_info, val, strict); if (result > 0) { return 1; @@ -3160,6 +3260,7 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert } } else { ZVAL_DEREF(val); + if (i_zend_check_property_type(prop_info, val, strict)) { return 1; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a4371db6f20be..007dffdddad7b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1014,6 +1014,16 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke child_info->offset = parent_info->offset; } + if (UNEXPECTED(child_info->flags & ZEND_ACC_FINAL && !(parent_info->flags & ZEND_ACC_FINAL))) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare non-final property %s::$%s as final %s::$%s", + ZSTR_VAL(parent_info->ce->name), + ZSTR_VAL(key), + ZSTR_VAL(ce->name), + ZSTR_VAL(key) + ); + } + if (UNEXPECTED(ZEND_TYPE_IS_SET(parent_info->type))) { inheritance_status status = property_types_compatible(parent_info, child_info); if (status == INHERITANCE_ERROR) { @@ -2052,7 +2062,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } } - /* property not found, so lets add it */ + /* property not found, so let's add it */ if (flags & ZEND_ACC_STATIC) { prop_value = &traits[i]->default_static_members_table[property_info->offset]; ZEND_ASSERT(Z_TYPE_P(prop_value) != IS_INDIRECT); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 57529afa3fc28..86ea980157685 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -374,6 +374,18 @@ static ZEND_COLD zend_never_inline void zend_bad_property_name(void) /* {{{ */ } /* }}} */ +static ZEND_COLD zend_never_inline void zend_final_property_assignment_error(zend_class_entry *ce, zend_string *property_name) /* {{{ */ +{ + zend_throw_error(NULL, "Cannot modify final property %s::$%s after initialization", ZSTR_VAL(ce->name), ZSTR_VAL(property_name)); +} +/* }}} */ + +static ZEND_COLD zend_never_inline void zend_final_property_unset_error(zend_class_entry *ce, zend_string *property_name) /* {{{ */ +{ + zend_throw_error(NULL, "Cannot unset final property %s::$%s", ZSTR_VAL(ce->name), ZSTR_VAL(property_name)); +} +/* }}} */ + static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *ce, zend_string *member, int silent, void **cache_slot, zend_property_info **info_ptr) /* {{{ */ { zval *zv; @@ -430,7 +442,7 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c goto dynamic; } else { wrong: - /* Information was available, but we were denied access. Error out. */ + /* Information was available, but we were denied access. Error out. */ if (!silent) { zend_bad_property_access(property_info, ce, member); } @@ -521,7 +533,7 @@ ZEND_API zend_property_info *zend_get_property_info(zend_class_entry *ce, zend_s goto dynamic; } else { wrong: - /* Information was available, but we were denied access. Error out. */ + /* Information was available, but we were denied access. Error out. */ if (!silent) { zend_bad_property_access(property_info, ce, member); } @@ -805,7 +817,14 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { variable_ptr = OBJ_PROP(zobj, property_offset); + if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { + if (UNEXPECTED(prop_info && prop_info->flags & ZEND_ACC_FINAL && Z_PROP_FLAG_P(variable_ptr) != IS_PROP_UNINIT)) { + zend_final_property_assignment_error(zobj->ce, name); + variable_ptr = &EG(error_zval); + goto exit; + } + Z_TRY_ADDREF_P(value); if (UNEXPECTED(prop_info)) { @@ -823,7 +842,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva goto exit; } if (Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT) { - /* Writes to uninitializde typed properties bypass __set(). */ + /* Writes to uninitialized typed properties bypass __set(). */ Z_PROP_FLAG_P(variable_ptr) = 0; goto write_std_property; } @@ -836,6 +855,12 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva zobj->properties = zend_array_dup(zobj->properties); } if ((variable_ptr = zend_hash_find(zobj->properties, name)) != NULL) { + if (UNEXPECTED(prop_info && prop_info->flags & ZEND_ACC_FINAL)) { + zend_final_property_assignment_error(zobj->ce, name); + variable_ptr = &EG(error_zval); + goto exit; + } + Z_TRY_ADDREF_P(value); goto found; } @@ -867,6 +892,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } } else { ZEND_ASSERT(!IS_WRONG_PROPERTY_OFFSET(property_offset)); + write_std_property: Z_TRY_ADDREF_P(value); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { @@ -1014,9 +1040,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { retval = OBJ_PROP(zobj, property_offset); + if (UNEXPECTED(Z_TYPE_P(retval) == IS_UNDEF)) { - if (EXPECTED(!zobj->ce->__get) || - UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET)) { + if (EXPECTED(!zobj->ce->__get) || UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET)) { if (UNEXPECTED(type == BP_VAR_RW || type == BP_VAR_R)) { if (UNEXPECTED(prop_info)) { zend_throw_error(NULL, @@ -1048,6 +1074,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam } if (EXPECTED(!zobj->ce->__get) || UNEXPECTED((*zend_get_property_guard(zobj, name)) & IN_GET)) { + if (UNEXPECTED(!zobj->properties)) { rebuild_object_properties(zobj); } @@ -1077,8 +1104,12 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void zval *slot = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(slot) != IS_UNDEF) { - if (UNEXPECTED(Z_ISREF_P(slot)) && - (ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) { + if (UNEXPECTED(prop_info && prop_info->flags & ZEND_ACC_FINAL)) { + zend_final_property_unset_error(zobj->ce, name); + return; + } + + if (UNEXPECTED(Z_ISREF_P(slot)) && (ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) { if (prop_info) { ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(slot), prop_info); } @@ -1114,7 +1145,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void if (zobj->ce->__unset) { uint32_t *guard = zend_get_property_guard(zobj, name); if (!((*guard) & IN_UNSET)) { - /* have unseter - try with it! */ + /* have unsetter - try with it! */ (*guard) |= IN_UNSET; /* prevent circular unsetting */ zend_std_call_unsetter(zobj, name); (*guard) &= ~IN_UNSET; @@ -1502,6 +1533,14 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend ret = CE_STATIC_MEMBERS(ce) + property_info->offset; ZVAL_DEINDIRECT(ret); + if (UNEXPECTED((type == BP_VAR_RW || type == BP_VAR_W) && property_info->flags & ZEND_ACC_FINAL && Z_TYPE_P(ret) != IS_UNDEF)) { + zend_throw_error(NULL, "Cannot modify final static property %s::$%s after initialization", + ZSTR_VAL(property_info->ce->name), + zend_get_unmangled_property_name(property_name) + ); + return NULL; + } + if (UNEXPECTED((type == BP_VAR_R || type == BP_VAR_RW) && Z_TYPE_P(ret) == IS_UNDEF && ZEND_TYPE_IS_SET(property_info->type))) { zend_throw_error(NULL, "Typed static property %s::$%s must not be accessed before initialization", diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 13a6b45a7ca0e..916bf2363621a 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -218,6 +218,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(dst), prop_info); } } + src++; dst++; } while (src != end); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 68ef127ef6105..852710f363c81 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2288,7 +2288,10 @@ ZEND_VM_HANDLER(97, ZEND_FETCH_OBJ_UNSET, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, C container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_UNSET); property = GET_OP2_ZVAL_PTR(BP_VAR_R); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, OP1_TYPE, property, OP2_TYPE, ((OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, OP1_TYPE, property, OP2_TYPE, + ((OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); FREE_OP2(); if (OP1_TYPE == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -6512,15 +6515,23 @@ ZEND_VM_HANDLER(126, ZEND_FE_FETCH_RW, VAR, ANY, JMP_ADDR) value_type = Z_TYPE_INFO_P(value); if (EXPECTED(value_type != IS_UNDEF) && EXPECTED(zend_check_property_access(Z_OBJ_P(array), p->key, 0) == SUCCESS)) { - if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) { - zend_property_info *prop_info = - zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value); - if (UNEXPECTED(prop_info)) { + zend_property_info *prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value); + if (UNEXPECTED(prop_info)) { + if (UNEXPECTED(prop_info->flags & ZEND_ACC_FINAL)) { + zend_throw_error(NULL, "Cannot acquire reference to final property %s::$%s", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) { ZVAL_NEW_REF(value, value); ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info); value_type = IS_REFERENCE_EX; } } + break; } } else if (EXPECTED(Z_OBJCE_P(array)->default_properties_count == 0) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f7155da46f969..545c70bee4e09 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -20678,15 +20678,23 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z value_type = Z_TYPE_INFO_P(value); if (EXPECTED(value_type != IS_UNDEF) && EXPECTED(zend_check_property_access(Z_OBJ_P(array), p->key, 0) == SUCCESS)) { - if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) { - zend_property_info *prop_info = - zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value); - if (UNEXPECTED(prop_info)) { + zend_property_info *prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value); + if (UNEXPECTED(prop_info)) { + if (UNEXPECTED(prop_info->flags & ZEND_ACC_FINAL)) { + zend_throw_error(NULL, "Cannot acquire reference to final property %s::$%s", + ZSTR_VAL(prop_info->ce->name), zend_get_unmangled_property_name(prop_info->name) + ); + UNDEF_RESULT(); + HANDLE_EXCEPTION(); + } + + if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) { ZVAL_NEW_REF(value, value); ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info); value_type = IS_REFERENCE_EX; } } + break; } } else if (EXPECTED(Z_OBJCE_P(array)->default_properties_count == 0) @@ -21453,7 +21461,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_VAR_CONST container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); property = RT_CONSTANT(opline, opline->op2); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_VAR, property, IS_CONST, ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_VAR, property, IS_CONST, + ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_VAR == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -23722,7 +23733,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_VAR_TMPVA container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); property = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_VAR, property, (IS_TMP_VAR|IS_VAR), (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_VAR, property, (IS_TMP_VAR|IS_VAR), + (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); if (IS_VAR == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -27174,7 +27188,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_VAR_CV_HA container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_VAR, property, IS_CV, ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_VAR, property, IS_CV, + ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_VAR == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -29693,7 +29710,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_UNUSED_CO container = &EX(This); property = RT_CONSTANT(opline, opline->op2); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_UNUSED, property, IS_CONST, ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_UNUSED, property, IS_CONST, + ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_UNUSED == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -31560,7 +31580,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_UNUSED_TM container = &EX(This); property = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_UNUSED, property, (IS_TMP_VAR|IS_VAR), (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_UNUSED, property, (IS_TMP_VAR|IS_VAR), + (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); if (IS_UNUSED == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -33955,7 +33978,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_UNUSED_CV container = &EX(This); property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_UNUSED, property, IS_CV, ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_UNUSED, property, IS_CV, + ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_UNUSED == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -38267,7 +38293,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_CV_CONST_ container = EX_VAR(opline->op1.var); property = RT_CONSTANT(opline, opline->op2); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_CV, property, IS_CONST, ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_CV, property, IS_CONST, + ((IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_CV == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -41745,7 +41774,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_CV_TMPVAR container = EX_VAR(opline->op1.var); property = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_CV, property, (IS_TMP_VAR|IS_VAR), (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_CV, property, (IS_TMP_VAR|IS_VAR), + (((IS_TMP_VAR|IS_VAR) == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); if (IS_CV == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); @@ -46553,7 +46585,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_OBJ_UNSET_SPEC_CV_CV_HAN container = EX_VAR(opline->op1.var); property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); result = EX_VAR(opline->result.var); - zend_fetch_property_address(result, container, IS_CV, property, IS_CV, ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, 0, 1 OPLINE_CC EXECUTE_DATA_CC); + zend_fetch_property_address(result, container, IS_CV, property, IS_CV, + ((IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL), BP_VAR_UNSET, + opline->extended_value & ZEND_FETCH_DIM_UNSET_FLAG, 1 OPLINE_CC EXECUTE_DATA_CC + ); if (IS_CV == IS_VAR) { FREE_VAR_PTR_AND_EXTRACT_RESULT_IF_NECESSARY(opline->op1.var); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 94f1b1659dfe0..92fe54f5b8e57 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5257,6 +5257,14 @@ ZEND_METHOD(reflection_property, isProtected) } /* }}} */ +/* {{{ proto public bool ReflectionProperty::isFinal() + Returns whether this property is final */ +ZEND_METHOD(reflection_property, isFinal) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); +} +/* }}} */ + /* {{{ proto public bool ReflectionProperty::isStatic() Returns whether this property is static */ ZEND_METHOD(reflection_property, isStatic) @@ -5286,7 +5294,7 @@ ZEND_METHOD(reflection_property, getModifiers) { reflection_object *intern; property_reference *ref; - uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC; + uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_FINAL; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -6360,6 +6368,7 @@ static const zend_function_entry reflection_property_functions[] = { ZEND_ME(reflection_property, isPublic, arginfo_class_ReflectionProperty_isPublic, 0) ZEND_ME(reflection_property, isPrivate, arginfo_class_ReflectionProperty_isPrivate, 0) ZEND_ME(reflection_property, isProtected, arginfo_class_ReflectionProperty_isProtected, 0) + ZEND_ME(reflection_property, isFinal, arginfo_class_ReflectionProperty_isFinal, 0) ZEND_ME(reflection_property, isStatic, arginfo_class_ReflectionProperty_isStatic, 0) ZEND_ME(reflection_property, isDefault, arginfo_class_ReflectionProperty_isDefault, 0) ZEND_ME(reflection_property, getModifiers, arginfo_class_ReflectionProperty_getModifiers, 0) diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 05e1b0490de5e..ffae411f3cad9 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -384,6 +384,9 @@ public function isPrivate() {} /** @return bool */ public function isProtected() {} + /** @return bool */ + public function isFinal() {} + /** @return bool */ public function isStatic() {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 82c048ad5c881..2643e29692a9f 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -299,6 +299,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isProtected arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract___clone + #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract___clone #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract___clone diff --git a/ext/reflection/tests/ReflectionProperty_basic1.phpt b/ext/reflection/tests/ReflectionProperty_basic1.phpt index 9bdf98b6e05ca..e5b0644b65018 100644 --- a/ext/reflection/tests/ReflectionProperty_basic1.phpt +++ b/ext/reflection/tests/ReflectionProperty_basic1.phpt @@ -17,6 +17,8 @@ function reflectProperty($class, $property) { var_dump($propInfo->isPrivate()); echo "isProtected():\n"; var_dump($propInfo->isProtected()); + echo "isFinal():\n"; + var_dump($propInfo->isFinal()); echo "isStatic():\n"; var_dump($propInfo->isStatic()); $instance = new $class(); @@ -34,7 +36,7 @@ class TestClass { public $pub; static public $stat = "static property"; protected $prot = 4; - private $priv = "keepOut"; + final private string $priv = "keepOut"; } reflectProperty("TestClass", "pub"); @@ -58,6 +60,8 @@ isPrivate(): bool(false) isProtected(): bool(false) +isFinal(): +bool(false) isStatic(): bool(false) getValue(): @@ -80,6 +84,8 @@ isPrivate(): bool(false) isProtected(): bool(false) +isFinal(): +bool(false) isStatic(): bool(true) getValue(): @@ -102,6 +108,8 @@ isPrivate(): bool(false) isProtected(): bool(true) +isFinal(): +bool(false) isStatic(): bool(false) @@ -120,6 +128,8 @@ isPrivate(): bool(true) isProtected(): bool(false) +isFinal(): +bool(true) isStatic(): bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_basic2.phpt b/ext/reflection/tests/ReflectionProperty_basic2.phpt index 09495f744bf28..8623b9d1ac5d9 100644 --- a/ext/reflection/tests/ReflectionProperty_basic2.phpt +++ b/ext/reflection/tests/ReflectionProperty_basic2.phpt @@ -27,7 +27,7 @@ class TestClass { * This property has a comment. */ protected $prot = 4; - private $priv = "keepOut"; + final private string $priv = "keepOut"; } reflectProperty("TestClass", "pub"); @@ -93,7 +93,7 @@ Reflecting on property TestClass::priv isDefault(): bool(true) getModifiers(): -int(4) +int(36) getDeclaringClass(): object(ReflectionClass)#%d (1) { ["name"]=> diff --git a/ext/reflection/tests/ReflectionProperty_setValueWithFinal.phpt b/ext/reflection/tests/ReflectionProperty_setValueWithFinal.phpt new file mode 100644 index 0000000000000..b41b785b977db --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_setValueWithFinal.phpt @@ -0,0 +1,26 @@ +--TEST-- +ReflectionClass::setValue() with final properties +--FILE-- +setValue($foo, "bar"); +var_dump($foo->a); + +$fooReflection = new ReflectionProperty(Foo::class, "b"); +try { + $fooReflection->setValue($foo, "bar"); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +string(3) "bar" +Cannot modify final property Foo::$b after initialization