From 8ad32d03a010a7da68b9fe60ded81db22f6e6903 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 21 Mar 2024 16:02:47 +0100 Subject: [PATCH 1/2] Implement structs --- .../tests/readonly_classes/readonly_enum.phpt | 2 +- .../readonly_classes/readonly_interface.phpt | 2 +- .../readonly_classes/readonly_trait.phpt | 2 +- Zend/tests/structs/abstract.phpt | 10 + Zend/tests/structs/array_access.phpt | 111 +++ Zend/tests/structs/assign_op.phpt | 27 + Zend/tests/structs/assign_ref.phpt | 27 + Zend/tests/structs/assign_to_self.phpt | 32 + Zend/tests/structs/basic.phpt | 33 + Zend/tests/structs/final.phpt | 10 + Zend/tests/structs/interior_immutability.phpt | 43 + Zend/tests/structs/is_identical.phpt | 73 ++ Zend/tests/structs/iter.phpt | 84 ++ .../structs/mutatin_in_ordinary_class.phpt | 12 + .../structs/mutating_call_verification.phpt | 38 + Zend/tests/structs/mutating_methods.phpt | 49 ++ Zend/tests/structs/nested.phpt | 45 + Zend/tests/structs/nested_references.phpt | 70 ++ Zend/tests/structs/non_mutating_method.phpt | 27 + Zend/tests/structs/pre_post_inc_dec.phpt | 70 ++ Zend/tests/structs/readonly.phpt | 25 + Zend/tests/structs/references.phpt | 53 ++ Zend/tests/structs/send.phpt | 46 ++ Zend/tests/structs/separating_fetch_this.phpt | 38 + Zend/tests/weakrefs/weakmap_structs.phpt | 37 + Zend/zend_API.c | 2 + Zend/zend_ast.c | 4 + Zend/zend_ast.h | 1 + Zend/zend_compile.c | 41 +- Zend/zend_compile.h | 12 +- Zend/zend_execute.c | 7 +- Zend/zend_language_parser.y | 23 +- Zend/zend_language_scanner.l | 14 + Zend/zend_object_handlers.c | 3 +- Zend/zend_operators.c | 64 +- Zend/zend_types.h | 13 + Zend/zend_vm_def.h | 77 +- Zend/zend_vm_execute.h | 781 ++++++++++++++++-- Zend/zend_weakrefs.c | 28 +- benchmark/benchmark.php | 6 +- build/gen_stub.php | 14 + ext/reflection/php_reflection.c | 14 + .../ReflectionProperty_setValue_structs.phpt | 35 + ext/spl/spl_array.c | 5 +- ext/spl/spl_observer.c | 64 ++ ext/spl/tests/ArrayObject_structs.phpt | 26 + ext/spl/tests/SplObjectStorage_structs.phpt | 57 ++ ext/tokenizer/tokenizer_data.c | 2 + ext/tokenizer/tokenizer_data.stub.php | 10 + ext/tokenizer/tokenizer_data_arginfo.h | 4 +- ext/zend_test/test.c | 10 + ext/zend_test/test.stub.php | 7 + ext/zend_test/test_arginfo.h | 29 +- ext/zend_test/tests/structs.phpt | 36 + 54 files changed, 2255 insertions(+), 100 deletions(-) create mode 100644 Zend/tests/structs/abstract.phpt create mode 100644 Zend/tests/structs/array_access.phpt create mode 100644 Zend/tests/structs/assign_op.phpt create mode 100644 Zend/tests/structs/assign_ref.phpt create mode 100644 Zend/tests/structs/assign_to_self.phpt create mode 100644 Zend/tests/structs/basic.phpt create mode 100644 Zend/tests/structs/final.phpt create mode 100644 Zend/tests/structs/interior_immutability.phpt create mode 100644 Zend/tests/structs/is_identical.phpt create mode 100644 Zend/tests/structs/iter.phpt create mode 100644 Zend/tests/structs/mutatin_in_ordinary_class.phpt create mode 100644 Zend/tests/structs/mutating_call_verification.phpt create mode 100644 Zend/tests/structs/mutating_methods.phpt create mode 100644 Zend/tests/structs/nested.phpt create mode 100644 Zend/tests/structs/nested_references.phpt create mode 100644 Zend/tests/structs/non_mutating_method.phpt create mode 100644 Zend/tests/structs/pre_post_inc_dec.phpt create mode 100644 Zend/tests/structs/readonly.phpt create mode 100644 Zend/tests/structs/references.phpt create mode 100644 Zend/tests/structs/send.phpt create mode 100644 Zend/tests/structs/separating_fetch_this.phpt create mode 100644 Zend/tests/weakrefs/weakmap_structs.phpt create mode 100644 ext/reflection/tests/ReflectionProperty_setValue_structs.phpt create mode 100644 ext/spl/tests/ArrayObject_structs.phpt create mode 100644 ext/spl/tests/SplObjectStorage_structs.phpt create mode 100644 ext/zend_test/tests/structs.phpt diff --git a/Zend/tests/readonly_classes/readonly_enum.phpt b/Zend/tests/readonly_classes/readonly_enum.phpt index afc1f235ce2da..025b4873a7a74 100644 --- a/Zend/tests/readonly_classes/readonly_enum.phpt +++ b/Zend/tests/readonly_classes/readonly_enum.phpt @@ -9,4 +9,4 @@ readonly enum Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "enum", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "enum" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_interface.phpt b/Zend/tests/readonly_classes/readonly_interface.phpt index c6fd42d5adca9..52283602058cd 100644 --- a/Zend/tests/readonly_classes/readonly_interface.phpt +++ b/Zend/tests/readonly_classes/readonly_interface.phpt @@ -9,4 +9,4 @@ readonly interface Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "interface" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_trait.phpt b/Zend/tests/readonly_classes/readonly_trait.phpt index 0e18d14bf8dcd..e170ecb1e0ab3 100644 --- a/Zend/tests/readonly_classes/readonly_trait.phpt +++ b/Zend/tests/readonly_classes/readonly_trait.phpt @@ -9,4 +9,4 @@ readonly trait Foo ?> --EXPECTF-- -Parse error: syntax error, unexpected token "trait", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d +Parse error: syntax error, unexpected token "trait" in %s on line %d diff --git a/Zend/tests/structs/abstract.phpt b/Zend/tests/structs/abstract.phpt new file mode 100644 index 0000000000000..45441ded36f37 --- /dev/null +++ b/Zend/tests/structs/abstract.phpt @@ -0,0 +1,10 @@ +--TEST-- +Structs must not be marked as abstract +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the abstract modifier on a struct, as structs are implicitly final in %s on line %d diff --git a/Zend/tests/structs/array_access.phpt b/Zend/tests/structs/array_access.phpt new file mode 100644 index 0000000000000..5b50e6045405c --- /dev/null +++ b/Zend/tests/structs/array_access.phpt @@ -0,0 +1,111 @@ +--TEST-- +Structs implementing ArrayAccess +--FILE-- +elements); + } + + public function offsetSet(mixed $offset, mixed $value): void { + if ($offset) { + $this->elements[$offset] = $value; + } else { + $this->elements[] = $value; + } + } + + public function offsetUnset(mixed $offset): void { + unset($this->elements[$offset]); + } +} + +struct VectorByVal extends VectorBase { + public function offsetGet(mixed $offset): mixed { + return $this->elements[$offset]; + } +} + +struct VectorByRef extends VectorBase { + public function &offsetGet(mixed $offset): mixed { + return $this->elements[$offset]; + } +} + +struct Box { + public function __construct( + public int $value, + ) {} + + public mutating function inc() { + $this->value++; + } +} + +$box = new Box(1); + +$vec = new VectorByVal(); +$vec[] = $box; +$vec[0]->value = 2; +var_dump($vec); +$vec[0]->inc!(); +var_dump($vec); + +$vec = new VectorByRef(); +$vec[] = $box; +$vec[0]->value = 2; +var_dump($vec); +$vec[0]->inc!(); +var_dump($vec); + +var_dump($box); + +?> +--EXPECT-- +object(VectorByVal)#2 (1) { + ["elements"]=> + array(1) { + [0]=> + object(Box)#1 (1) { + ["value"]=> + int(1) + } + } +} +object(VectorByVal)#2 (1) { + ["elements"]=> + array(1) { + [0]=> + object(Box)#1 (1) { + ["value"]=> + int(1) + } + } +} +object(VectorByRef)#3 (1) { + ["elements"]=> + array(1) { + [0]=> + object(Box)#2 (1) { + ["value"]=> + int(2) + } + } +} +object(VectorByRef)#3 (1) { + ["elements"]=> + array(1) { + [0]=> + object(Box)#2 (1) { + ["value"]=> + int(3) + } + } +} +object(Box)#1 (1) { + ["value"]=> + int(1) +} diff --git a/Zend/tests/structs/assign_op.phpt b/Zend/tests/structs/assign_op.phpt new file mode 100644 index 0000000000000..80d089fee455d --- /dev/null +++ b/Zend/tests/structs/assign_op.phpt @@ -0,0 +1,27 @@ +--TEST-- +Assign op on structs +--FILE-- +value += 1; +var_dump($a); +var_dump($b); + +?> +--EXPECT-- +object(Box)#1 (1) { + ["value"]=> + int(0) +} +object(Box)#2 (1) { + ["value"]=> + int(1) +} diff --git a/Zend/tests/structs/assign_ref.phpt b/Zend/tests/structs/assign_ref.phpt new file mode 100644 index 0000000000000..0ad01bd930362 --- /dev/null +++ b/Zend/tests/structs/assign_ref.phpt @@ -0,0 +1,27 @@ +--TEST-- +Assign ref on structs +--FILE-- +value = 1; +$b = $a; +$c = 2; +$b->value = &$c; +var_dump($a); +var_dump($b); + +?> +--EXPECT-- +object(Box)#1 (1) { + ["value"]=> + int(1) +} +object(Box)#2 (1) { + ["value"]=> + &int(2) +} diff --git a/Zend/tests/structs/assign_to_self.phpt b/Zend/tests/structs/assign_to_self.phpt new file mode 100644 index 0000000000000..1405c322d1367 --- /dev/null +++ b/Zend/tests/structs/assign_to_self.phpt @@ -0,0 +1,32 @@ +--TEST-- +Send structs +--FILE-- +value = $a; +var_dump($a); + +$b = new Box(42); +$b->value = &$b; +var_dump($b); + +?> +--EXPECT-- +object(Box)#2 (1) { + ["value"]=> + object(Box)#1 (1) { + ["value"]=> + int(42) + } +} +object(Box)#3 (1) { + ["value"]=> + *RECURSION* +} diff --git a/Zend/tests/structs/basic.phpt b/Zend/tests/structs/basic.phpt new file mode 100644 index 0000000000000..69ac4451e1cb5 --- /dev/null +++ b/Zend/tests/structs/basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +Basic structs +--FILE-- +x = 2; +$b->y = 2; +var_dump($a); +var_dump($b); + +?> +--EXPECT-- +object(Point)#1 (2) { + ["x"]=> + int(1) + ["y"]=> + int(1) +} +object(Point)#2 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) +} diff --git a/Zend/tests/structs/final.phpt b/Zend/tests/structs/final.phpt new file mode 100644 index 0000000000000..5f128b44505bc --- /dev/null +++ b/Zend/tests/structs/final.phpt @@ -0,0 +1,10 @@ +--TEST-- +Structs must not be marked as final +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the final modifier on a struct, as structs are implicitly final in %s on line %d diff --git a/Zend/tests/structs/interior_immutability.phpt b/Zend/tests/structs/interior_immutability.phpt new file mode 100644 index 0000000000000..74dfe398005b7 --- /dev/null +++ b/Zend/tests/structs/interior_immutability.phpt @@ -0,0 +1,43 @@ +--TEST-- +Basic structs +--FILE-- +writableChild->value = 2; +try { + $parent->readonlyChild->value = 2; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +var_dump($parent); + +?> +--EXPECTF-- +Cannot indirectly modify readonly property Parent_::$readonlyChild +object(Parent_)#1 (2) { + ["writableChild"]=> + object(Child)#%d (1) { + ["value"]=> + int(2) + } + ["readonlyChild"]=> + object(Child)#%d (1) { + ["value"]=> + int(1) + } +} diff --git a/Zend/tests/structs/is_identical.phpt b/Zend/tests/structs/is_identical.phpt new file mode 100644 index 0000000000000..18043455a1672 --- /dev/null +++ b/Zend/tests/structs/is_identical.phpt @@ -0,0 +1,73 @@ +--TEST-- +=== on structs +--FILE-- +value++; +$e = new Box('1'); + +$values = [$a, $b, $c, $d, $e]; +foreach ($values as $l) { + foreach ($values as $r) { + echo var_export($l->value, true) + . ' === ' + . var_export($r->value, true) + . ' => ' + . ($l === $r ? 'true' : 'false') + . "\n"; + } +} + +#[\AllowDynamicProperties] +struct Point {} + +$a = new Point(); +$a->x = 1; +$a->y = 1; +$b = new Point(); +$b->y = 1; +$b->x = 1; +var_dump($a === $b); +unset($b->y); +$b->y = 1; +var_dump($a === $b); + +?> +--EXPECT-- +1 === 1 => true +1 === 2 => false +1 === 1 => true +1 === 2 => false +1 === '1' => false +2 === 1 => false +2 === 2 => true +2 === 1 => false +2 === 2 => true +2 === '1' => false +1 === 1 => true +1 === 2 => false +1 === 1 => true +1 === 2 => false +1 === '1' => false +2 === 1 => false +2 === 2 => true +2 === 1 => false +2 === 2 => true +2 === '1' => false +'1' === 1 => false +'1' === 2 => false +'1' === 1 => false +'1' === 2 => false +'1' === '1' => true +bool(false) +bool(true) diff --git a/Zend/tests/structs/iter.phpt b/Zend/tests/structs/iter.phpt new file mode 100644 index 0000000000000..bf17d6b8ee46e --- /dev/null +++ b/Zend/tests/structs/iter.phpt @@ -0,0 +1,84 @@ +--TEST-- +Iteration of structs +--FILE-- +value = 1; +$copy = $box; + +echo "Iteration by value\n"; + +foreach ($box as $value) { + var_dump($value); +} + +echo "\nIteration by ref\n"; + +foreach ($box as $prop => &$value) { + if ($prop === 'value' && $value === 1) { + $box->dynamic = 1; + } + $value++; + var_dump($value); +} + +echo "\nIteration by ref on copy\n"; + +$copy2 = $copy; +$copy2Ref = &$copy2; + +foreach ($copy2Ref as $prop => &$value) { + $value++; + var_dump($value); +} + +echo "\nIteration by ref on ref with separation\n"; + +function &getBox() { + global $box; + $box2 = $box; + return $box2; +} + +foreach (getBox() as $prop => &$value) { + $value++; + var_dump($value); +} + +echo "\nDone\n"; + +var_dump($box, $copy); + +?> +--EXPECT-- +Iteration by value +int(1) + +Iteration by ref +int(2) +int(2) + +Iteration by ref on copy +int(2) + +Iteration by ref on ref with separation +int(3) +int(3) + +Done +object(Box)#2 (2) { + ["value"]=> + int(2) + ["dynamic"]=> + int(2) +} +object(Box)#1 (1) { + ["value"]=> + int(1) +} diff --git a/Zend/tests/structs/mutatin_in_ordinary_class.phpt b/Zend/tests/structs/mutatin_in_ordinary_class.phpt new file mode 100644 index 0000000000000..4363f85ca46f0 --- /dev/null +++ b/Zend/tests/structs/mutatin_in_ordinary_class.phpt @@ -0,0 +1,12 @@ +--TEST-- +Mutating keyword in ordinary class +--FILE-- + +--EXPECTF-- +Fatal error: Mutating modifier may only be added to struct methods in %s on line %d diff --git a/Zend/tests/structs/mutating_call_verification.phpt b/Zend/tests/structs/mutating_call_verification.phpt new file mode 100644 index 0000000000000..381e9b4187461 --- /dev/null +++ b/Zend/tests/structs/mutating_call_verification.phpt @@ -0,0 +1,38 @@ +--TEST-- +Mutating method calls on structs +--FILE-- +nonMutating(); +$d->mutating!(); + +try { + $d->nonMutating!(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + $d->mutating(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +nonMutating +mutating +Non-mutating method must not be called with $object->func!() syntax +Mutating method must be called with $object->func!() syntax diff --git a/Zend/tests/structs/mutating_methods.phpt b/Zend/tests/structs/mutating_methods.phpt new file mode 100644 index 0000000000000..ff8027c8984d3 --- /dev/null +++ b/Zend/tests/structs/mutating_methods.phpt @@ -0,0 +1,49 @@ +--TEST-- +Mutating method calls on structs +--FILE-- +x = 0; + $this->y = 0; + } +} + +struct Shape { + public function __construct( + public Point $position, + ) {} +} + +$s = new Shape(new Point(1, 1)); +$s2 = $s; +$s2->position->zero!(); +var_dump($s); +var_dump($s2); + +?> +--EXPECT-- +object(Shape)#1 (1) { + ["position"]=> + object(Point)#2 (2) { + ["x"]=> + int(1) + ["y"]=> + int(1) + } +} +object(Shape)#3 (1) { + ["position"]=> + object(Point)#4 (2) { + ["x"]=> + int(0) + ["y"]=> + int(0) + } +} diff --git a/Zend/tests/structs/nested.phpt b/Zend/tests/structs/nested.phpt new file mode 100644 index 0000000000000..00b7ec3255363 --- /dev/null +++ b/Zend/tests/structs/nested.phpt @@ -0,0 +1,45 @@ +--TEST-- +Nested structs +--FILE-- +position->x = 2; +$s2->position->y = 2; +var_dump($s); +var_dump($s2); + +?> +--EXPECT-- +object(Shape)#1 (1) { + ["position"]=> + object(Point)#2 (2) { + ["x"]=> + int(1) + ["y"]=> + int(1) + } +} +object(Shape)#3 (1) { + ["position"]=> + object(Point)#4 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) + } +} diff --git a/Zend/tests/structs/nested_references.phpt b/Zend/tests/structs/nested_references.phpt new file mode 100644 index 0000000000000..9f0bd3ae3b41a --- /dev/null +++ b/Zend/tests/structs/nested_references.phpt @@ -0,0 +1,70 @@ +--TEST-- +Structs in nested references +--FILE-- +x = 0; + $this->y = 0; + } +} + +struct Shape { + public function __construct( + public Point &$position, + ) {} +} + +$p = new Point(1, 1); +$s = new Shape($p); + +$p->x = 2; +$p->y = 2; +var_dump($p); +var_dump($s); + +$s->position->zero!(); +var_dump($p); +var_dump($s); + +unset($p); +unset($s); + +?> +--EXPECT-- +object(Point)#1 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) +} +object(Shape)#2 (1) { + ["position"]=> + &object(Point)#1 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) + } +} +object(Point)#1 (2) { + ["x"]=> + int(0) + ["y"]=> + int(0) +} +object(Shape)#2 (1) { + ["position"]=> + &object(Point)#1 (2) { + ["x"]=> + int(0) + ["y"]=> + int(0) + } +} diff --git a/Zend/tests/structs/non_mutating_method.phpt b/Zend/tests/structs/non_mutating_method.phpt new file mode 100644 index 0000000000000..4580fe1249d22 --- /dev/null +++ b/Zend/tests/structs/non_mutating_method.phpt @@ -0,0 +1,27 @@ +--TEST-- +Non-mutating methods do not cause separation +--FILE-- +test(); +$b->test(); + +var_dump($a, $b); + +?> +--EXPECT-- +string(10) "Test::test" +string(10) "Test::test" +object(Test)#1 (0) { +} +object(Test)#1 (0) { +} diff --git a/Zend/tests/structs/pre_post_inc_dec.phpt b/Zend/tests/structs/pre_post_inc_dec.phpt new file mode 100644 index 0000000000000..fcb66f8441949 --- /dev/null +++ b/Zend/tests/structs/pre_post_inc_dec.phpt @@ -0,0 +1,70 @@ +--TEST-- +++/-- on structs +--FILE-- +value; + } +} + +$original = new Box(0); + +$preDec = $original; +var_dump(--$preDec->value); + +$preInc = $original; +var_dump(++$preInc->value); + +$postDec = $original; +var_dump($postDec->value--); + +$postInc = $original; +var_dump($postInc->value++); + +$preDecMethod = $original; +var_dump($preDecMethod->preDec!()); + +var_dump($preDec); +var_dump($preInc); +var_dump($postDec); +var_dump($postInc); +var_dump($preDecMethod); +var_dump($original); + +?> +--EXPECT-- +int(-1) +int(1) +int(0) +int(0) +NULL +object(Box)#2 (1) { + ["value"]=> + int(-1) +} +object(Box)#3 (1) { + ["value"]=> + int(1) +} +object(Box)#4 (1) { + ["value"]=> + int(-1) +} +object(Box)#5 (1) { + ["value"]=> + int(1) +} +object(Box)#6 (1) { + ["value"]=> + int(-1) +} +object(Box)#1 (1) { + ["value"]=> + int(0) +} diff --git a/Zend/tests/structs/readonly.phpt b/Zend/tests/structs/readonly.phpt new file mode 100644 index 0000000000000..29b74bb249440 --- /dev/null +++ b/Zend/tests/structs/readonly.phpt @@ -0,0 +1,25 @@ +--TEST-- +Structs may be marked as readonly +--FILE-- +value = 43; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool(true) +Cannot modify readonly property Box::$value diff --git a/Zend/tests/structs/references.phpt b/Zend/tests/structs/references.phpt new file mode 100644 index 0000000000000..72b899101f8c9 --- /dev/null +++ b/Zend/tests/structs/references.phpt @@ -0,0 +1,53 @@ +--TEST-- +Structs in references +--FILE-- +x = 0; + $this->y = 0; + } +} + +$p = new Point(1, 1); +$pRef = &$p; +$pRef->x = 2; +$pRef->y = 2; +var_dump($p); +var_dump($pRef); +$pRef->zero!(); +var_dump($p); +var_dump($pRef); + +?> +--EXPECT-- +object(Point)#1 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) +} +object(Point)#1 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) +} +object(Point)#1 (2) { + ["x"]=> + int(0) + ["y"]=> + int(0) +} +object(Point)#1 (2) { + ["x"]=> + int(0) + ["y"]=> + int(0) +} diff --git a/Zend/tests/structs/send.phpt b/Zend/tests/structs/send.phpt new file mode 100644 index 0000000000000..51900c0776987 --- /dev/null +++ b/Zend/tests/structs/send.phpt @@ -0,0 +1,46 @@ +--TEST-- +Send structs +--FILE-- +value = 2; + var_dump($box); +} + +function byRef(&$box) { + $box->value = 2; + var_dump($box); +} + +$box = new Box(); +$box->value = 1; + +byVal($box); +var_dump($box); + +byRef($box); +var_dump($box); + +?> +--EXPECT-- +object(Box)#2 (1) { + ["value"]=> + int(2) +} +object(Box)#1 (1) { + ["value"]=> + int(1) +} +object(Box)#1 (1) { + ["value"]=> + int(2) +} +object(Box)#1 (1) { + ["value"]=> + int(2) +} diff --git a/Zend/tests/structs/separating_fetch_this.phpt b/Zend/tests/structs/separating_fetch_this.phpt new file mode 100644 index 0000000000000..1a5276d158cb9 --- /dev/null +++ b/Zend/tests/structs/separating_fetch_this.phpt @@ -0,0 +1,38 @@ +--TEST-- +FETCH_THIS in struct method must separate +--FILE-- +x = $x; + $this->y = $y; + return $self; + } +} + +$a = new Point(1, 1); +$b = $a->replace(2, 2); +var_dump($a); +var_dump($b); + +?> +--EXPECT-- +object(Point)#1 (2) { + ["x"]=> + int(2) + ["y"]=> + int(2) +} +object(Point)#2 (2) { + ["x"]=> + int(1) + ["y"]=> + int(1) +} diff --git a/Zend/tests/weakrefs/weakmap_structs.phpt b/Zend/tests/weakrefs/weakmap_structs.phpt new file mode 100644 index 0000000000000..dd34395e031a0 --- /dev/null +++ b/Zend/tests/weakrefs/weakmap_structs.phpt @@ -0,0 +1,37 @@ +--TEST-- +WeakMaps disallow structs +--FILE-- +getMessage(), "\n"; + } +} + +test(function ($map, $dc) { + var_dump($map[$dc]); +}); +test(function ($map, $dc) { + $map[$dc] = 1; +}); +test(function ($map, $dc) { + unset($map[$dc]); +}); +test(function ($map, $dc) { + var_dump(isset($map[$dc])); +}); + +?> +--EXPECT-- +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e0006e7d7275f..60504a939dff1 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -5287,6 +5287,8 @@ ZEND_API ZEND_COLD const char *zend_get_object_type_case(const zend_class_entry return upper_case ? "Interface" : "interface"; } else if (ce->ce_flags & ZEND_ACC_ENUM) { return upper_case ? "Enum" : "enum"; + } else if (ce->ce_flags & ZEND_ACC_STRUCT) { + return upper_case ? "Struct" : "struct"; } else { return upper_case ? "Class" : "class"; } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 2551f876d4465..abad7a3cb86f7 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2668,9 +2668,13 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio /* 3 child nodes */ case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: zend_ast_export_ex(str, ast->child[0], 0, indent); smart_str_appends(str, ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL ? "?->" : "->"); zend_ast_export_var(str, ast->child[1], 0, indent); + if (ast->kind == ZEND_AST_MUTATING_METHOD_CALL) { + smart_str_appendc(str, '!'); + } smart_str_appendc(str, '('); zend_ast_export_ex(str, ast->child[2], 0, indent); smart_str_appendc(str, ')'); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..6cbf2dbb3014c 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -169,6 +169,7 @@ enum _zend_ast_kind { // Pseudo node for initializing enums ZEND_AST_CONST_ENUM_INIT, + ZEND_AST_MUTATING_METHOD_CALL, /* 4 child nodes */ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2ad3f6b323d8f..5cca581d80774 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -870,6 +870,8 @@ static const char *zend_modifier_token_to_string(uint32_t token) return "protected(set)"; case T_PRIVATE_SET: return "private(set)"; + case T_MUTATING: + return "mutating"; EMPTY_SWITCH_DEFAULT_CASE() } } @@ -930,6 +932,11 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token return ZEND_ACC_PRIVATE_SET; } break; + case T_MUTATING: + if (target == ZEND_MODIFIER_TARGET_METHOD) { + return ZEND_ACC_MUTATING; + } + break; } char *member; @@ -1000,6 +1007,16 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ "Cannot use the final modifier on an abstract class", 0); return 0; } + if ((new_flags & ZEND_ACC_STRUCT) && (new_flags & ZEND_ACC_FINAL)) { + zend_throw_exception(zend_ce_compile_error, + "Cannot use the final modifier on a struct, as structs are implicitly final", 0); + return 0; + } + if ((new_flags & ZEND_ACC_STRUCT) && (new_flags & ZEND_ACC_ABSTRACT)) { + zend_throw_exception(zend_ce_compile_error, + "Cannot use the abstract modifier on a struct, as structs are implicitly final", 0); + return 0; + } return new_flags; } /* }}} */ @@ -2495,6 +2512,7 @@ static bool zend_ast_kind_is_short_circuited(zend_ast_kind ast_kind) case ZEND_AST_STATIC_PROP: case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: case ZEND_AST_STATIC_CALL: return 1; default: @@ -2509,6 +2527,7 @@ static bool zend_ast_is_short_circuited(const zend_ast *ast) case ZEND_AST_PROP: case ZEND_AST_STATIC_PROP: case ZEND_AST_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: case ZEND_AST_STATIC_CALL: return zend_ast_is_short_circuited(ast->child[0]); case ZEND_AST_NULLSAFE_PROP: @@ -2730,6 +2749,7 @@ static inline bool zend_is_call(zend_ast *ast) /* {{{ */ return ast->kind == ZEND_AST_CALL || ast->kind == ZEND_AST_METHOD_CALL || ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL + || ast->kind == ZEND_AST_MUTATING_METHOD_CALL || ast->kind == ZEND_AST_STATIC_CALL; } /* }}} */ @@ -3374,6 +3394,7 @@ static void zend_ensure_writable_variable(const zend_ast *ast) /* {{{ */ if ( ast->kind == ZEND_AST_METHOD_CALL || ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL + || ast->kind == ZEND_AST_MUTATING_METHOD_CALL || ast->kind == ZEND_AST_STATIC_CALL ) { zend_error_noreturn(E_COMPILE_ERROR, "Can't use method return value in write context"); @@ -3483,7 +3504,7 @@ static void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_NULLSAFE_PROP: offset = zend_delayed_compile_begin(); zend_delayed_compile_prop(result, var_ast, BP_VAR_W); - zend_compile_expr(&expr_node, expr_ast); + zend_compile_expr_with_potential_assign_to_self(&expr_node, expr_ast, var_ast); opline = zend_delayed_compile_end(offset); opline->opcode = ZEND_ASSIGN_OBJ; @@ -3958,7 +3979,7 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, zend_fun zend_do_extended_fcall_begin(); opline = &CG(active_op_array)->opcodes[opnum_init]; - opline->extended_value = arg_count; + opline->extended_value |= arg_count; if (opline->opcode == ZEND_INIT_FCALL) { opline->op1.num = zend_vm_calc_used_stack(arg_count, fbc); @@ -5206,6 +5227,7 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type zend_op *opline; zend_function *fbc = NULL; bool nullsafe = ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL; + bool mutating = ast->kind == ZEND_AST_MUTATING_METHOD_CALL; uint32_t short_circuiting_checkpoint = zend_short_circuiting_checkpoint(); if (is_this_fetch(obj_ast)) { @@ -5220,7 +5242,11 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type * check for a nullsafe access. */ } else { zend_short_circuiting_mark_inner(obj_ast); - zend_compile_expr(&obj_node, obj_ast); + if (mutating && zend_is_variable(obj_ast)) { + zend_compile_var(&obj_node, obj_ast, BP_VAR_RW, /* by_ref */ false); + } else { + zend_compile_expr(&obj_node, obj_ast); + } if (nullsafe) { zend_emit_jmp_null(&obj_node, type); } @@ -5228,6 +5254,9 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type zend_compile_expr(&method_node, method_ast); opline = zend_emit_op(NULL, ZEND_INIT_METHOD_CALL, &obj_node, NULL); + if (mutating) { + opline->extended_value |= ZEND_INIT_METHOD_CALL_MUTATING; + } if (method_node.op_type == IS_CONST) { if (Z_TYPE(method_node.u.constant) != IS_STRING) { @@ -8289,6 +8318,9 @@ static zend_op_array *zend_compile_func_decl_ex( } else if (is_method) { bool has_body = stmt_ast != NULL; lcname = zend_begin_method_decl(op_array, decl->name, has_body); + if ((op_array->fn_flags & ZEND_ACC_MUTATING) && !(op_array->scope->ce_flags & ZEND_ACC_STRUCT)) { + zend_error_noreturn(E_COMPILE_ERROR, "Mutating modifier may only be added to struct methods"); + } } else { lcname = zend_begin_func_decl(result, op_array, decl, level); if (decl->kind == ZEND_AST_ARROW_FUNC) { @@ -11615,6 +11647,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_CALL: case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: case ZEND_AST_STATIC_CALL: case ZEND_AST_PARENT_PROPERTY_HOOK_CALL: zend_compile_var(result, ast, BP_VAR_R, 0); @@ -11749,6 +11782,7 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty case ZEND_AST_CALL: case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: case ZEND_AST_STATIC_CALL: zend_compile_memoized_expr(result, ast); /* This might not actually produce an opcode, e.g. for expressions evaluated at comptime. */ @@ -11774,6 +11808,7 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty return NULL; case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: + case ZEND_AST_MUTATING_METHOD_CALL: zend_compile_method_call(result, ast, type); return NULL; case ZEND_AST_STATIC_CALL: diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 4f7d158ba0f1c..f611cf23a3f62 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -267,7 +267,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -333,7 +333,10 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ -/* Function Flags (unused: 30) | | | */ +/* Structs are value types | | | */ +#define ZEND_ACC_STRUCT (1 << 30) /* X | | | */ +/* | | | */ +/* Function Flags (unused: none) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ @@ -398,6 +401,9 @@ typedef struct _zend_oparray_context { /* has #[\NoDiscard] attribute | | | */ #define ZEND_ACC_NODISCARD (1 << 29) /* | X | | */ /* | | | */ +/* Mutating function of a struct | | | */ +#define ZEND_ACC_MUTATING (1 << 30) /* | X | | */ +/* | | | */ /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ @@ -1107,6 +1113,8 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS 1 +#define ZEND_INIT_METHOD_CALL_MUTATING ((uint32_t)1 << 31) + /* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */ #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT #define _ZEND_IS_VARIADIC_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 2)) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 89fbcf2cbd781..df4b4ef669160 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3422,6 +3422,10 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } while (0); } + if (container_op_type & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(container); + } + zobj = Z_OBJ_P(container); if (prop_op_type == IS_CONST && EXPECTED(zobj->ce == CACHED_PTR_EX(cache_slot))) { @@ -3442,7 +3446,8 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c * Similar as with magic __get() allow them, but return the value as a copy * to make sure no actual modification is possible. */ ZEND_ASSERT(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET); - if (Z_TYPE_P(ptr) == IS_OBJECT) { + if (Z_TYPE_P(ptr) == IS_OBJECT + && EXPECTED(!(Z_OBJCE_P(ptr)->ce_flags & ZEND_ACC_STRUCT))) { ZVAL_COPY(result, ptr); } else { if (prop_info->flags & ZEND_ACC_READONLY) { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9483a83b4e955..adef88d4ec1c0 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -151,6 +151,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_STATIC "'static'" %token T_ABSTRACT "'abstract'" %token T_FINAL "'final'" +%token T_MUTATING "'mutating'" %token T_PRIVATE "'private'" %token T_PROTECTED "'protected'" %token T_PUBLIC "'public'" @@ -164,6 +165,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_EMPTY "'empty'" %token T_HALT_COMPILER "'__halt_compiler'" %token T_CLASS "'class'" +%token T_STRUCT "'struct'" %token T_TRAIT "'trait'" %token T_INTERFACE "'interface'" %token T_ENUM "'enum'" @@ -290,6 +292,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers %type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags +%type class_like %type backup_lex_pos %type backup_doc_comment @@ -308,14 +311,14 @@ reserved_non_modifiers: | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK - | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS + | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_STRUCT | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM | T_PROPERTY_C ; semi_reserved: reserved_non_modifiers - | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY + | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY | T_MUTATING ; ampersand: @@ -323,6 +326,11 @@ ampersand: | T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG ; +class_like: + T_CLASS { $$ = 0; } + | T_STRUCT { $$ = ZEND_ACC_STRUCT; } +; + identifier: T_STRING { $$ = $1; } | semi_reserved { @@ -596,12 +604,12 @@ is_variadic: ; class_declaration_statement: - class_modifiers T_CLASS { $$ = CG(zend_lineno); } + class_modifiers class_like { $$ = zend_add_class_modifier($1, $2); if (!$$) { YYERROR; } } { $$ = CG(zend_lineno); } T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL); } - | T_CLASS { $$ = CG(zend_lineno); } + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $3, $4, $8, zend_ast_get_str($5), $6, $7, $10, NULL, NULL); } + | class_like { $$ = CG(zend_lineno); } T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); } + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); } ; class_modifiers: @@ -1078,6 +1086,7 @@ member_modifier: | T_ABSTRACT { $$ = T_ABSTRACT; } | T_FINAL { $$ = T_FINAL; } | T_READONLY { $$ = T_READONLY; } + | T_MUTATING { $$ = T_MUTATING; } ; property_list: @@ -1517,6 +1526,8 @@ callable_variable: { $$ = zend_ast_create(ZEND_AST_METHOD_CALL, $1, $3, $4); } | array_object_dereferenceable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list { $$ = zend_ast_create(ZEND_AST_NULLSAFE_METHOD_CALL, $1, $3, $4); } + | array_object_dereferenceable T_OBJECT_OPERATOR property_name '!' argument_list + { $$ = zend_ast_create(ZEND_AST_MUTATING_METHOD_CALL, $1, $3, $5); } | function_call { $$ = $1; } ; diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 4c883b81c5f7d..5666c1942e4cb 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1562,6 +1562,16 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ENUM); } +/* Same rules as "enum" apply. */ +"struct"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { + yyless(6); + RETURN_TOKEN_WITH_STR(T_STRING, 0); +} +"struct"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { + yyless(6); + RETURN_TOKEN_WITH_IDENT(T_STRUCT); +} + "extends" { RETURN_TOKEN_WITH_IDENT(T_EXTENDS); } @@ -1721,6 +1731,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_FINAL); } +"mutating" { + RETURN_TOKEN_WITH_IDENT(T_MUTATING); +} + "private" { RETURN_TOKEN_WITH_IDENT(T_PRIVATE); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index a31c7d2afdeee..6a83b5691b1c4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -736,7 +736,8 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int 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) && ((prop_info->flags & ZEND_ACC_READONLY) || !zend_asymmetric_property_has_set_access(prop_info))) { - if (Z_TYPE_P(retval) == IS_OBJECT) { + if (Z_TYPE_P(retval) == IS_OBJECT + && EXPECTED(!(Z_OBJCE_P(retval)->ce_flags & ZEND_ACC_STRUCT))) { /* 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 * to make sure no actual modification is possible. */ diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 879141d1a139e..d899eadbee305 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -2403,6 +2403,61 @@ static int hash_zval_identical_function(zval *z1, zval *z2) /* {{{ */ } /* }}} */ +static bool zend_is_identical_struct(const zval *op1, const zval *op2) /* {{{ */ +{ + ZEND_ASSERT(Z_TYPE_P(op1) == IS_OBJECT); + ZEND_ASSERT(Z_TYPE_P(op2) == IS_OBJECT); + + zend_object *obj1 = Z_OBJ_P(op1); + zend_object *obj2 = Z_OBJ_P(op2); + + /* Should be handled by fast path. */ + ZEND_ASSERT(obj1 != obj2); + ZEND_ASSERT(obj1->ce == obj2->ce); + + if (!obj1->properties && !obj2->properties) { + if (!obj1->ce->default_properties_count) { + return true; + } + + /* It's enough to protect only one of the objects. The second one may be + * referenced from the first and this may cause false recursion + * detection. */ + if (UNEXPECTED(Z_IS_RECURSIVE_P(op1))) { + zend_error_noreturn(E_ERROR, "Nesting level too deep - recursive dependency?"); + } + Z_PROTECT_RECURSION_P(op1); + + for (int i = 0; i < obj1->ce->default_properties_count; i++) { + zend_property_info *info = obj1->ce->properties_info_table[i]; + if (!info) { + continue; + } + + zval *p1 = OBJ_PROP(obj1, info->offset); + zval *p2 = OBJ_PROP(obj2, info->offset); + if (!zend_is_identical(p1, p2)) { + Z_UNPROTECT_RECURSION_P(op1); + return false; + } + } + + Z_UNPROTECT_RECURSION_P(op1); + return true; + } else { + if (!obj1->properties) { + zend_std_get_properties(obj1); + } + if (!obj2->properties) { + zend_std_get_properties(obj2); + } + return zend_hash_compare( + obj1->properties, obj2->properties, + (compare_func_t) hash_zval_identical_function, + /* ordered */ true) == 0; + } +} + ZEND_API bool ZEND_FASTCALL zend_is_identical(const zval *op1, const zval *op2) /* {{{ */ { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { @@ -2425,7 +2480,14 @@ ZEND_API bool ZEND_FASTCALL zend_is_identical(const zval *op1, const zval *op2) return (Z_ARRVAL_P(op1) == Z_ARRVAL_P(op2) || zend_hash_compare(Z_ARRVAL_P(op1), Z_ARRVAL_P(op2), (compare_func_t) hash_zval_identical_function, 1) == 0); case IS_OBJECT: - return (Z_OBJ_P(op1) == Z_OBJ_P(op2)); + if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) { + return true; + } + if (UNEXPECTED(Z_OBJCE_P(op1)->ce_flags & ZEND_ACC_STRUCT) + && Z_OBJCE_P(op1) == Z_OBJCE_P(op2)) { + return zend_is_identical_struct(op1, op2); + } + return false; default: return 0; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 7676a1d42a5f4..b06596d8668c5 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -1556,6 +1556,19 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) { } \ } while (0) +#define SEPARATE_DATA_OBJ(zv) do { \ + zval *_zv = (zv); \ + ZEND_ASSERT(Z_TYPE_P(_zv) == IS_OBJECT); \ + zend_object *obj = Z_OBJ_P(_zv); \ + if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_STRUCT) \ + && GC_REFCOUNT(obj) > 1) { \ + zend_object_clone_obj_t clone_call = obj->handlers->clone_obj; \ + ZEND_ASSERT(clone_call); \ + ZVAL_OBJ(_zv, clone_call(obj)); \ + GC_TRY_DELREF(obj); \ + } \ + } while (0) + #define SEPARATE_ZVAL_NOREF(zv) do { \ zval *_zv = (zv); \ ZEND_ASSERT(Z_TYPE_P(_zv) != IS_REFERENCE); \ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9317d1ff592f5..43e1749432327 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -1040,6 +1040,9 @@ ZEND_VM_HANDLER(28, ZEND_ASSIGN_OBJ_OP, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, OP) ZEND_VM_C_LABEL(assign_op_object): /* here we are sure we are dealing with an object */ + if (OP1_TYPE & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (OP2_TYPE == IS_CONST) { name = Z_STR_P(property); @@ -1317,6 +1320,9 @@ ZEND_VM_HANDLER(132, ZEND_PRE_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACH ZEND_VM_C_LABEL(pre_incdec_object): /* here we are sure we are dealing with an object */ + if (OP1_TYPE & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (OP2_TYPE == IS_CONST) { name = Z_STR_P(property); @@ -1387,6 +1393,9 @@ ZEND_VM_HANDLER(134, ZEND_POST_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CAC ZEND_VM_C_LABEL(post_incdec_object): /* here we are sure we are dealing with an object */ + if (OP1_TYPE & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (OP2_TYPE == IS_CONST) { name = Z_STR_P(property); @@ -2484,6 +2493,9 @@ ZEND_VM_HANDLER(24, ZEND_ASSIGN_OBJ, VAR|UNUSED|THIS|CV, CONST|TMPVAR|CV, CACHE_ } ZEND_VM_C_LABEL(assign_object): + if (OP1_TYPE & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (OP2_TYPE == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -3585,10 +3597,21 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R); + if ((OP1_TYPE & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R); + } if (OP2_TYPE != IS_CONST) { function_name = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R); @@ -3605,13 +3628,17 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, } else if (OP2_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { - FREE_OP1(); + if (!needs_addref) { + FREE_OP1(); + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); FREE_OP2(); - FREE_OP1(); + if (!needs_addref) { + FREE_OP1(); + } HANDLE_EXCEPTION(); } while (0); } @@ -3621,6 +3648,9 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, } else { do { if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (OP1_TYPE & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((OP1_TYPE & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -3628,8 +3658,11 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (OP1_TYPE & IS_VAR) { + if ((OP1_TYPE & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -3653,7 +3686,9 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, } zend_invalid_method_call(object, function_name); FREE_OP2(); - FREE_OP1(); + if (!needs_addref) { + FREE_OP1(); + } HANDLE_EXCEPTION(); } } while (0); @@ -3683,6 +3718,21 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + FREE_OP2(); + if ((OP1_TYPE & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (OP2_TYPE == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -3715,7 +3765,7 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (OP1_TYPE & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (OP1_TYPE == IS_CV) { + if (OP1_TYPE == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -3723,7 +3773,7 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV, } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -6992,6 +7042,9 @@ ZEND_VM_COLD_CONST_HANDLER(125, ZEND_FE_RESET_RW, CONST|TMP|VAR|CV, JMP_ADDR) ZEND_VM_NEXT_OPCODE(); } else if (OP1_TYPE != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + if (OP1_TYPE & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(array_ptr); + } zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; if (UNEXPECTED(zend_object_is_lazy(zobj))) { @@ -9179,8 +9232,14 @@ ZEND_VM_HOT_HANDLER(184, ZEND_FETCH_THIS, UNUSED, UNUSED) if (EXPECTED(Z_TYPE(EX(This)) == IS_OBJECT)) { zval *result = EX_VAR(opline->result.var); - ZVAL_OBJ(result, Z_OBJ(EX(This))); - Z_ADDREF_P(result); + if (EXPECTED(!(Z_OBJCE(EX(This))->ce_flags & ZEND_ACC_STRUCT))) { + ZVAL_OBJ(result, Z_OBJ(EX(This))); + Z_ADDREF_P(result); + } else { + zend_object_clone_obj_t clone_call = Z_OBJ(EX(This))->handlers->clone_obj; + ZEND_ASSERT(clone_call); + ZVAL_OBJ(result, clone_call(Z_OBJ(EX(This)))); + } ZEND_VM_NEXT_OPCODE(); } else { ZEND_VM_DISPATCH_TO_HELPER(zend_this_not_in_object_context_helper); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f7bec6f7198c8..82ed3e94584d6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5599,6 +5599,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_ ZEND_VM_NEXT_OPCODE(); } else if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + if (IS_CONST & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(array_ptr); + } zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; if (UNEXPECTED(zend_object_is_lazy(zobj))) { @@ -7213,10 +7216,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = RT_CONSTANT(opline, opline->op1); + if ((IS_CONST & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = RT_CONSTANT(opline, opline->op1); + } if (IS_CONST != IS_CONST) { function_name = RT_CONSTANT(opline, opline->op2); @@ -7233,13 +7247,17 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -7249,6 +7267,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else { do { if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CONST & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -7256,8 +7277,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CONST & IS_VAR) { + if ((IS_CONST & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -7281,7 +7305,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -7311,6 +7337,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_CONST & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CONST == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -7343,7 +7384,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CONST & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CONST == IS_CV) { + if (IS_CONST == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -7351,7 +7392,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -9791,10 +9832,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = RT_CONSTANT(opline, opline->op1); + if ((IS_CONST & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = RT_CONSTANT(opline, opline->op1); + } if ((IS_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -9811,13 +9863,17 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -9827,6 +9883,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else { do { if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CONST & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -9834,8 +9893,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CONST & IS_VAR) { + if ((IS_CONST & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -9859,7 +9921,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -9889,6 +9953,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if ((IS_CONST & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if ((IS_TMP_VAR|IS_VAR) == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -9921,7 +10000,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CONST & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CONST == IS_CV) { + if (IS_CONST == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -9929,7 +10008,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -12282,10 +12361,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = RT_CONSTANT(opline, opline->op1); + if ((IS_CONST & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = RT_CONSTANT(opline, opline->op1); + } if (IS_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -12302,13 +12392,17 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -12318,6 +12412,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } else { do { if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CONST & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -12325,8 +12422,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CONST & IS_VAR) { + if ((IS_CONST & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -12350,7 +12450,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -12380,6 +12482,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_CONST & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -12412,7 +12529,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CONST & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CONST == IS_CV) { + if (IS_CONST == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -12420,7 +12537,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_ } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -16685,10 +16802,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + } if (IS_CONST != IS_CONST) { function_name = RT_CONSTANT(opline, opline->op2); @@ -16705,13 +16833,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } while (0); } @@ -16721,6 +16853,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } else { do { if ((IS_TMP_VAR|IS_VAR) != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -16728,8 +16863,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if ((IS_TMP_VAR|IS_VAR) & IS_VAR) { + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -16753,7 +16891,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } zend_invalid_method_call(object, function_name); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } while (0); @@ -16783,6 +16923,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CONST == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -16815,7 +16970,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if ((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if ((IS_TMP_VAR|IS_VAR) == IS_CV) { + if ((IS_TMP_VAR|IS_VAR) == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -16823,7 +16978,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -18178,10 +18333,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + } if ((IS_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -18198,13 +18364,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T } else if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } while (0); } @@ -18214,6 +18384,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T } else { do { if ((IS_TMP_VAR|IS_VAR) != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -18221,8 +18394,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if ((IS_TMP_VAR|IS_VAR) & IS_VAR) { + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -18246,7 +18422,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } while (0); @@ -18276,6 +18454,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if ((IS_TMP_VAR|IS_VAR) == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -18308,7 +18501,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if ((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if ((IS_TMP_VAR|IS_VAR) == IS_CV) { + if ((IS_TMP_VAR|IS_VAR) == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -18316,7 +18509,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_T } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -19585,10 +19778,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); + } if (IS_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -19605,13 +19809,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } while (0); } @@ -19621,6 +19829,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } else { do { if ((IS_TMP_VAR|IS_VAR) != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if ((IS_TMP_VAR|IS_VAR) & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -19628,8 +19839,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if ((IS_TMP_VAR|IS_VAR) & IS_VAR) { + if (((IS_TMP_VAR|IS_VAR) & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -19653,7 +19867,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } zend_invalid_method_call(object, function_name); - zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + if (!needs_addref) { + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + } HANDLE_EXCEPTION(); } } while (0); @@ -19683,6 +19899,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -19715,7 +19946,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if ((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if ((IS_TMP_VAR|IS_VAR) == IS_CV) { + if ((IS_TMP_VAR|IS_VAR) == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -19723,7 +19954,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMPVAR_C } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -20356,6 +20587,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_TMP_HANDLER(Z ZEND_VM_NEXT_OPCODE(); } else if (IS_TMP_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + if (IS_TMP_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(array_ptr); + } zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; if (UNEXPECTED(zend_object_is_lazy(zobj))) { @@ -23025,6 +23259,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_VAR_HANDLER(Z ZEND_VM_NEXT_OPCODE(); } else if (IS_VAR != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(array_ptr); + } zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; if (UNEXPECTED(zend_object_is_lazy(zobj))) { @@ -23632,6 +23869,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_VAR_CONST_H assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -23852,6 +24092,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_VAR_CONST_HAN pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -23916,6 +24159,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_VAR_CONST_HA post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -24127,6 +24373,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -24281,6 +24530,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -24435,6 +24687,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -24589,6 +24844,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -26622,6 +26880,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_VAR_TMPVAR_ assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -26844,6 +27105,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_VAR_TMPVAR_HA pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -26909,6 +27173,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_VAR_TMPVAR_H post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -27122,6 +27389,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -27276,6 +27546,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -27430,6 +27703,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -27584,6 +27860,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -30979,6 +31258,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_VAR_CV_HAND assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -31199,6 +31481,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_VAR_CV_HANDLE pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -31263,6 +31548,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_VAR_CV_HANDL post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_VAR & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -31474,6 +31762,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -31628,6 +31919,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -31782,6 +32076,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -31936,6 +32233,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA } assign_object: + if (IS_VAR & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -33648,6 +33948,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_UNUSED_CONS assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -33738,6 +34041,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_UNUSED_CONST_ pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -33802,6 +34108,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_UNUSED_CONST post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -34221,6 +34530,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -34375,6 +34687,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -34529,6 +34844,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -34683,6 +35001,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -34973,10 +35294,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = &EX(This); + if ((IS_UNUSED & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = &EX(This); + } if (IS_CONST != IS_CONST) { function_name = RT_CONSTANT(opline, opline->op2); @@ -34993,13 +35325,17 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -35009,6 +35345,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } else { do { if (IS_UNUSED != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_UNUSED & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_UNUSED & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -35016,8 +35355,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_UNUSED & IS_VAR) { + if ((IS_UNUSED & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -35041,7 +35383,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -35071,6 +35415,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_UNUSED & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CONST == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -35103,7 +35462,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_UNUSED & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_UNUSED == IS_CV) { + if (IS_UNUSED == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -35111,7 +35470,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -35823,6 +36182,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_UNUSED_TMPV assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -35913,6 +36275,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_UNUSED_TMPVAR pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -35978,6 +36343,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_UNUSED_TMPVA post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -36393,6 +36761,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -36547,6 +36918,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -36701,6 +37075,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -36855,6 +37232,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -37146,10 +37526,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = &EX(This); + if ((IS_UNUSED & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = &EX(This); + } if ((IS_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -37166,13 +37557,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T } else if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -37182,6 +37577,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T } else { do { if (IS_UNUSED != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_UNUSED & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_UNUSED & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -37189,8 +37587,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_UNUSED & IS_VAR) { + if ((IS_UNUSED & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -37214,7 +37615,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -37244,6 +37647,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if ((IS_UNUSED & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if ((IS_TMP_VAR|IS_VAR) == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -37276,7 +37694,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_UNUSED & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_UNUSED == IS_CV) { + if (IS_UNUSED == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -37284,7 +37702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_T } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -38203,8 +38621,14 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_THIS_SPEC_UN if (EXPECTED(Z_TYPE(EX(This)) == IS_OBJECT)) { zval *result = EX_VAR(opline->result.var); - ZVAL_OBJ(result, Z_OBJ(EX(This))); - Z_ADDREF_P(result); + if (EXPECTED(!(Z_OBJCE(EX(This))->ce_flags & ZEND_ACC_STRUCT))) { + ZVAL_OBJ(result, Z_OBJ(EX(This))); + Z_ADDREF_P(result); + } else { + zend_object_clone_obj_t clone_call = Z_OBJ(EX(This))->handlers->clone_obj; + ZEND_ASSERT(clone_call); + ZVAL_OBJ(result, clone_call(Z_OBJ(EX(This)))); + } ZEND_VM_NEXT_OPCODE(); } else { ZEND_VM_TAIL_CALL(zend_this_not_in_object_context_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); @@ -38470,6 +38894,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_UNUSED_CV_H assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -38560,6 +38987,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_UNUSED_CV_HAN pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -38624,6 +39054,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_UNUSED_CV_HA post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_UNUSED & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -39038,6 +39471,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -39192,6 +39628,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -39346,6 +39785,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -39500,6 +39942,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D } assign_object: + if (IS_UNUSED & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -39790,10 +40235,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = &EX(This); + if ((IS_UNUSED & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = &EX(This); + } if (IS_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -39810,13 +40266,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -39826,6 +40286,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C } else { do { if (IS_UNUSED != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_UNUSED & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_UNUSED & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -39833,8 +40296,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_UNUSED & IS_VAR) { + if ((IS_UNUSED & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -39858,7 +40324,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -39888,6 +40356,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_UNUSED & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -39920,7 +40403,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_UNUSED & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_UNUSED == IS_CV) { + if (IS_UNUSED == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -39928,7 +40411,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_C } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -41344,6 +41827,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_RESET_RW_SPEC_CV_HANDLER(ZE ZEND_VM_NEXT_OPCODE(); } else if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(array_ptr) == IS_OBJECT)) { if (!Z_OBJCE_P(array_ptr)->get_iterator) { + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(array_ptr); + } zend_object *zobj = Z_OBJ_P(array_ptr); HashTable *properties; if (UNEXPECTED(zend_object_is_lazy(zobj))) { @@ -42623,6 +43109,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_CV_CONST_HA assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -42843,6 +43332,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_CV_CONST_HAND pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -42907,6 +43399,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_CV_CONST_HAN post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { name = Z_STR_P(property); @@ -43438,6 +43933,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -43592,6 +44090,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -43746,6 +44247,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -43900,6 +44404,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -44906,10 +45413,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = EX_VAR(opline->op1.var); + if ((IS_CV & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = EX_VAR(opline->op1.var); + } if (IS_CONST != IS_CONST) { function_name = RT_CONSTANT(opline, opline->op2); @@ -44926,13 +45444,17 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -44942,6 +45464,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } else { do { if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CV & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -44949,8 +45474,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CV & IS_VAR) { + if ((IS_CV & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -44974,7 +45502,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -45004,6 +45534,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_CV & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CONST == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -45036,7 +45581,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CV & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CV == IS_CV) { + if (IS_CV == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -45044,7 +45589,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_S } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -46576,6 +47121,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_CV_TMPVAR_H assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -46798,6 +47346,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_CV_TMPVAR_HAN pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -46863,6 +47414,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_CV_TMPVAR_HA post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { name = Z_STR_P(property); @@ -47390,6 +47944,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -47544,6 +48101,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -47698,6 +48258,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -47852,6 +48415,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if ((IS_TMP_VAR|IS_VAR) == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -48800,10 +49366,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = EX_VAR(opline->op1.var); + if ((IS_CV & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = EX_VAR(opline->op1.var); + } if ((IS_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -48820,13 +49397,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA } else if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -48836,6 +49417,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA } else { do { if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CV & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -48843,8 +49427,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CV & IS_VAR) { + if ((IS_CV & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -48868,7 +49455,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -48898,6 +49487,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if ((IS_CV & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if ((IS_TMP_VAR|IS_VAR) == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -48930,7 +49534,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CV & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CV == IS_CV) { + if (IS_CV == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -48938,7 +49542,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMPVA } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; @@ -52078,6 +52682,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_OP_SPEC_CV_CV_HANDL assign_op_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -52298,6 +52905,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PRE_INC_OBJ_SPEC_CV_CV_HANDLER pre_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -52362,6 +52972,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_POST_INC_OBJ_SPEC_CV_CV_HANDLE post_incdec_object: /* here we are sure we are dealing with an object */ + if (IS_CV & (IS_VAR|IS_CV)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { name = Z_STR_P(property); @@ -52888,6 +53501,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -53042,6 +53658,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -53196,6 +53815,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -53350,6 +53972,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ } assign_object: + if (IS_CV & (IS_CV|IS_VAR)) { + SEPARATE_DATA_OBJ(object); + } zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { @@ -54394,10 +55019,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA zend_object *obj; zend_execute_data *call; uint32_t call_info; + bool needs_addref = false; SAVE_OPLINE(); - object = EX_VAR(opline->op1.var); + if ((IS_CV & IS_VAR) + && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + object = EX_VAR(opline->op1.var); + if (EXPECTED(Z_TYPE_P(object) == IS_INDIRECT)) { + object = Z_INDIRECT_P(object); + /* Object was fetched through BP_VAR_RW, we need to addref. */ + needs_addref = true; + } + } else { + object = EX_VAR(opline->op1.var); + } if (IS_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -54414,13 +55050,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA } else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(function_name) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); if (UNEXPECTED(EG(exception) != NULL)) { + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } zend_throw_error(NULL, "Method name must be a string"); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } while (0); } @@ -54430,6 +55070,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA } else { do { if (IS_CV != IS_CONST && EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (IS_CV & (IS_CV|IS_VAR) && UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); } else { if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_ISREF_P(object))) { @@ -54437,8 +55080,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA object = &ref->val; if (EXPECTED(Z_TYPE_P(object) == IS_OBJECT)) { + if (UNEXPECTED(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING)) { + SEPARATE_DATA_OBJ(object); + } obj = Z_OBJ_P(object); - if (IS_CV & IS_VAR) { + if ((IS_CV & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -54462,7 +55108,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA } zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -54492,6 +55140,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA } HANDLE_EXCEPTION(); } + if (UNEXPECTED( + (bool)(fbc->common.fn_flags & ZEND_ACC_MUTATING) + != (bool)(opline->extended_value & ZEND_INIT_METHOD_CALL_MUTATING) + )) { + if (fbc->common.fn_flags & ZEND_ACC_MUTATING) { + zend_throw_error(NULL, "Mutating method must be called with $object->func!() syntax"); + } else { + zend_throw_error(NULL, "Non-mutating method must not be called with $object->func!() syntax"); + } + + if ((IS_CV & (IS_VAR|IS_TMP_VAR)) && !needs_addref && GC_DELREF(orig_obj) == 0) { + zend_objects_store_del(orig_obj); + } + HANDLE_EXCEPTION(); + } if (IS_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -54524,7 +55187,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA obj = (zend_object*)called_scope; call_info = ZEND_CALL_NESTED_FUNCTION; } else if (IS_CV & (IS_VAR|IS_TMP_VAR|IS_CV)) { - if (IS_CV == IS_CV) { + if (IS_CV == IS_CV || needs_addref) { GC_ADDREF(obj); /* For $this pointer */ } /* CV may be changed indirectly (e.g. when it's a reference) */ @@ -54532,7 +55195,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HA } call = zend_vm_stack_push_call_frame(call_info, - fbc, opline->extended_value, obj); + fbc, opline->extended_value & ~ZEND_INIT_METHOD_CALL_MUTATING, obj); call->prev_execute_data = EX(call); EX(call) = call; diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index cf363cd12595c..aa7352696bbee 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -348,6 +348,22 @@ static void zend_weakmap_free_obj(zend_object *object) zend_object_std_dtor(&wm->std); } +static zend_result zend_weakmap_check_offset(zval *offset) +{ + if (Z_TYPE_P(offset) != IS_OBJECT) { + zend_type_error("WeakMap key must be an object"); + return FAILURE; + } + + zend_object *obj = Z_OBJ_P(offset); + if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_STRUCT)) { + zend_type_error("Instance of struct %s may not be used as key", ZSTR_VAL(obj->ce->name)); + return FAILURE; + } + + return SUCCESS; +} + static zval *zend_weakmap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { if (offset == NULL) { @@ -356,8 +372,7 @@ static zval *zend_weakmap_read_dimension(zend_object *object, zval *offset, int } ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) != IS_OBJECT) { - zend_type_error("WeakMap key must be an object"); + if (zend_weakmap_check_offset(offset) == FAILURE) { return NULL; } @@ -387,8 +402,7 @@ static void zend_weakmap_write_dimension(zend_object *object, zval *offset, zval } ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) != IS_OBJECT) { - zend_type_error("WeakMap key must be an object"); + if (zend_weakmap_check_offset(offset) == FAILURE) { return; } @@ -417,8 +431,7 @@ static void zend_weakmap_write_dimension(zend_object *object, zval *offset, zval static int zend_weakmap_has_dimension(zend_object *object, zval *offset, int check_empty) { ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) != IS_OBJECT) { - zend_type_error("WeakMap key must be an object"); + if (zend_weakmap_check_offset(offset) == FAILURE) { return 0; } @@ -437,8 +450,7 @@ static int zend_weakmap_has_dimension(zend_object *object, zval *offset, int che static void zend_weakmap_unset_dimension(zend_object *object, zval *offset) { ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) != IS_OBJECT) { - zend_type_error("WeakMap key must be an object"); + if (zend_weakmap_check_offset(offset) == FAILURE) { return; } diff --git a/benchmark/benchmark.php b/benchmark/benchmark.php index 0c2ac4c6010a4..6da117e887ff6 100644 --- a/benchmark/benchmark.php +++ b/benchmark/benchmark.php @@ -28,11 +28,11 @@ function main() { $data['branch'] = $branch; } $data['Zend/bench.php'] = runBench(false); - $data['Zend/bench.php JIT'] = runBench(true); + // $data['Zend/bench.php JIT'] = runBench(true); $data['Symfony Demo 2.2.3'] = runSymfonyDemo(false); - $data['Symfony Demo 2.2.3 JIT'] = runSymfonyDemo(true); + // $data['Symfony Demo 2.2.3 JIT'] = runSymfonyDemo(true); $data['Wordpress 6.2'] = runWordpress(false); - $data['Wordpress 6.2 JIT'] = runWordpress(true); + // $data['Wordpress 6.2 JIT'] = runWordpress(true); $result = json_encode($data, JSON_PRETTY_PRINT) . "\n"; fwrite(STDOUT, $result); diff --git a/build/gen_stub.php b/build/gen_stub.php index e69846733a9d9..c57a6f750be55 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -3273,6 +3273,7 @@ class ClassInfo { public array $attributes; private ?ExposedDocComment $exposedDocComment; private bool $isNotSerializable; + private bool $isStruct; /** @var Name[] */ public /* readonly */ array $extends; /** @var Name[] */ @@ -3309,6 +3310,7 @@ public function __construct( array $attributes, ?ExposedDocComment $exposedDocComment, bool $isNotSerializable, + bool $isStruct, array $extends, array $implements, array $constInfos, @@ -3329,6 +3331,7 @@ public function __construct( $this->attributes = $attributes; $this->exposedDocComment = $exposedDocComment; $this->isNotSerializable = $isNotSerializable; + $this->isStruct = $isStruct; $this->extends = $extends; $this->implements = $implements; $this->constInfos = $constInfos; @@ -3550,6 +3553,12 @@ private function getFlagsByPhpVersion(): array $php70Flags[] = "ZEND_ACC_DEPRECATED"; } + /* Only available from 8.5, but must not be used in older versions at + * all. Hence, don't add a version guard. */ + if ($this->isStruct) { + $php70Flags[] = "ZEND_ACC_STRUCT"; + } + $php80Flags = $php70Flags; if ($this->isStrictProperties) { @@ -3596,6 +3605,7 @@ public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibil $this->exposedDocComment = null; $this->isStrictProperties = false; $this->isNotSerializable = false; + $this->isStruct = false; foreach ($this->propertyInfos as $propertyInfo) { $propertyInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); @@ -4667,6 +4677,7 @@ function parseClass( $isDeprecated = false; $isStrictProperties = false; $isNotSerializable = false; + $isStruct = false; $allowsDynamicProperties = false; if ($comments) { @@ -4680,6 +4691,8 @@ function parseClass( $isStrictProperties = true; } else if ($tag->name === 'not-serializable') { $isNotSerializable = true; + } else if ($tag->name === 'struct') { + $isStruct = true; } else if ($tag->name === 'undocumentable') { $isUndocumentable = true; } @@ -4738,6 +4751,7 @@ function parseClass( $attributes, ExposedDocComment::extractExposedComment($comments), $isNotSerializable, + $isStruct, $extends, $implements, $consts, diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bff617cfb61de..2481c48704399 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3464,6 +3464,13 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic) _DO_THROW("Given object is not an instance of the class this method was declared in"); RETURN_THROWS(); } + + if (UNEXPECTED(Z_OBJCE_P(object)->ce_flags & ZEND_ACC_STRUCT)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "May not invoke mutating method \"%s::%s()\" through reflection", + ZSTR_VAL(Z_OBJCE_P(object)->name), ZSTR_VAL(mptr->common.function_name)); + RETURN_THROWS(); + } } /* Copy the zend_function when calling via handler (e.g. Closure::__invoke()) */ callback = _copy_function(mptr); @@ -5967,6 +5974,13 @@ ZEND_METHOD(ReflectionProperty, setValue) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); + if (UNEXPECTED(object->ce->ce_flags & ZEND_ACC_STRUCT)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "May not set property value of struct \"%s\" through reflection", + ZSTR_VAL(object->ce->name)); + RETURN_THROWS(); + } + zend_class_entry *old_scope = EG(fake_scope); EG(fake_scope) = intern->ce; object->handlers->write_property(object, ref->unmangled_name, value, ref->cache_slot); diff --git a/ext/reflection/tests/ReflectionProperty_setValue_structs.phpt b/ext/reflection/tests/ReflectionProperty_setValue_structs.phpt new file mode 100644 index 0000000000000..83eba52dad63a --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_setValue_structs.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test ReflectionProperty::setValue() on structs +--FILE-- +value = null; + } +} + +$box = new Box(1); + +$reflection = new ReflectionProperty(Box::class, 'value'); +try { + $reflection->setValue($box, 2); +} catch (Exception $ex) { + echo get_class($ex) . ': ' . $ex->getMessage(), "\n"; +} + +$reflection = new ReflectionMethod(Box::class, 'setNull'); +try { + $reflection->invoke($box); +} catch (Exception $ex) { + echo get_class($ex) . ': ' . $ex->getMessage(), "\n"; +} + +?> +--EXPECT-- +ReflectionException: May not set property value of struct "Box" through reflection +ReflectionException: May not invoke mutating method "Box::setNull()" through reflection diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 937e9f41b845e..fb6272867ab88 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -1004,9 +1004,10 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF); return; } - if (UNEXPECTED(Z_OBJCE_P(array)->ce_flags & ZEND_ACC_ENUM)) { + if (UNEXPECTED(Z_OBJCE_P(array)->ce_flags & (ZEND_ACC_ENUM|ZEND_ACC_STRUCT))) { zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, - "Enums are not compatible with %s", + "%s are not compatible with %s", + (Z_OBJCE_P(array)->ce_flags & ZEND_ACC_ENUM) ? "Enums" : "Structs", ZSTR_VAL(intern->std.ce->name)); ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF); return; diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index af9c598f08b08..4aee46bb3e680 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -78,6 +78,15 @@ static void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */ zend_hash_destroy(&intern->storage); } /* }}} */ +static zend_result spl_check_struct(zend_object *obj) +{ + if (UNEXPECTED(obj->ce->ce_flags & ZEND_ACC_STRUCT)) { + zend_type_error("Instance of struct %s may not be used as key", ZSTR_VAL(obj->ce->name)); + return FAILURE; + } + return SUCCESS; +} + static zend_result spl_object_storage_get_hash(zend_hash_key *key, spl_SplObjectStorage *intern, zend_object *obj) { if (UNEXPECTED(intern->fptr_get_hash)) { zval param; @@ -433,12 +442,25 @@ PHP_METHOD(SplObjectStorage, attach) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(inf) ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED(spl_check_struct(obj) == FAILURE)) { + RETURN_THROWS(); + } + spl_object_storage_attach(intern, obj, inf); } /* }}} */ // todo: make spl_object_storage_has_dimension return bool as well static int spl_object_storage_has_dimension(zend_object *object, zval *offset, int check_empty) { + if (EXPECTED(offset)) { + ZVAL_DEREF(offset); + if (Z_TYPE_P(offset) == IS_OBJECT + && UNEXPECTED(spl_check_struct(Z_OBJ_P(offset)) == FAILURE)) { + return 0; + } + } + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) { /* Can't optimize empty()/isset() check if getHash, offsetExists, or offsetGet is overridden */ @@ -458,6 +480,14 @@ static int spl_object_storage_has_dimension(zend_object *object, zval *offset, i static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { + if (EXPECTED(offset)) { + ZVAL_DEREF(offset); + if (Z_TYPE_P(offset) == IS_OBJECT + && UNEXPECTED(spl_check_struct(Z_OBJ_P(offset)) == FAILURE)) { + return NULL; + } + } + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_READ_DIMENSION))) { /* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */ @@ -481,6 +511,14 @@ static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf) { + if (EXPECTED(offset)) { + ZVAL_DEREF(offset); + if (Z_TYPE_P(offset) == IS_OBJECT + && UNEXPECTED(spl_check_struct(Z_OBJ_P(offset)) == FAILURE)) { + return; + } + } + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_WRITE_DIMENSION))) { zend_std_write_dimension(object, offset, inf); @@ -491,6 +529,14 @@ static void spl_object_storage_write_dimension(zend_object *object, zval *offset static void spl_object_storage_unset_dimension(zend_object *object, zval *offset) { + if (EXPECTED(offset)) { + ZVAL_DEREF(offset); + if (Z_TYPE_P(offset) == IS_OBJECT + && UNEXPECTED(spl_check_struct(Z_OBJ_P(offset)) == FAILURE)) { + return; + } + } + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); if (UNEXPECTED(Z_TYPE_P(offset) != IS_OBJECT || (intern->flags & SOS_OVERRIDDEN_UNSET_DIMENSION))) { zend_std_unset_dimension(object, offset); @@ -508,6 +554,11 @@ PHP_METHOD(SplObjectStorage, detach) ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJ(obj) ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED(spl_check_struct(obj) == FAILURE)) { + RETURN_THROWS(); + } + spl_object_storage_detach(intern, obj); zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); @@ -523,6 +574,10 @@ PHP_METHOD(SplObjectStorage, getHash) Z_PARAM_OBJ(obj) ZEND_PARSE_PARAMETERS_END(); + if (UNEXPECTED(spl_check_struct(obj) == FAILURE)) { + RETURN_THROWS(); + } + RETURN_NEW_STR(php_spl_object_hash(obj)); } /* }}} */ @@ -539,6 +594,10 @@ PHP_METHOD(SplObjectStorage, offsetGet) Z_PARAM_OBJ(obj) ZEND_PARSE_PARAMETERS_END(); + if (UNEXPECTED(spl_check_struct(obj) == FAILURE)) { + RETURN_THROWS(); + } + if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) { RETURN_NULL(); } @@ -634,6 +693,11 @@ PHP_METHOD(SplObjectStorage, contains) ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJ(obj) ZEND_PARSE_PARAMETERS_END(); + + if (UNEXPECTED(spl_check_struct(obj) == FAILURE)) { + RETURN_THROWS(); + } + RETURN_BOOL(spl_object_storage_contains(intern, obj)); } /* }}} */ diff --git a/ext/spl/tests/ArrayObject_structs.phpt b/ext/spl/tests/ArrayObject_structs.phpt new file mode 100644 index 0000000000000..b05ca869589ac --- /dev/null +++ b/ext/spl/tests/ArrayObject_structs.phpt @@ -0,0 +1,26 @@ +--TEST-- +SPL: ArrayObject disallow structs +--FILE-- +getMessage(), "\n"; +} + +try { + $ao = new ArrayObject([]); + $ao->exchangeArray($dc); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Structs are not compatible with ArrayObject +Structs are not compatible with ArrayObject diff --git a/ext/spl/tests/SplObjectStorage_structs.phpt b/ext/spl/tests/SplObjectStorage_structs.phpt new file mode 100644 index 0000000000000..1a09e9802432c --- /dev/null +++ b/ext/spl/tests/SplObjectStorage_structs.phpt @@ -0,0 +1,57 @@ +--TEST-- +SplObjectStorage disallows structs +--FILE-- +getMessage(), "\n"; + } +} + +test(function ($map, $dc) { + var_dump($map[$dc]); +}); +test(function ($map, $dc) { + $map[$dc] = 1; +}); +test(function ($map, $dc) { + unset($map[$dc]); +}); +test(function ($map, $dc) { + var_dump(isset($map[$dc])); +}); +test(function ($map, $dc) { + $map->attach($dc, 1); +}); +test(function ($map, $dc) { + $map->detach($dc); +}); +test(function ($map, $dc) { + var_dump($map->getHash($dc)); +}); +test(function ($map, $dc) { + var_dump($map->offsetGet($dc)); +}); +test(function ($map, $dc) { + var_dump($map->contains($dc)); +}); + +?> +--EXPECT-- +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key +Instance of struct DC may not be used as key diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a1e131032bcfb..021f9e74a8032 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -89,6 +89,7 @@ char *get_token_type_name(int token_type) case T_STATIC: return "T_STATIC"; case T_ABSTRACT: return "T_ABSTRACT"; case T_FINAL: return "T_FINAL"; + case T_MUTATING: return "T_MUTATING"; case T_PRIVATE: return "T_PRIVATE"; case T_PROTECTED: return "T_PROTECTED"; case T_PUBLIC: return "T_PUBLIC"; @@ -102,6 +103,7 @@ char *get_token_type_name(int token_type) case T_EMPTY: return "T_EMPTY"; case T_HALT_COMPILER: return "T_HALT_COMPILER"; case T_CLASS: return "T_CLASS"; + case T_STRUCT: return "T_STRUCT"; case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; case T_ENUM: return "T_ENUM"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index c1e1fd254dfaa..936c4fff27970 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -322,6 +322,11 @@ * @cvalue T_FINAL */ const T_FINAL = UNKNOWN; +/** + * @var int + * @cvalue T_MUTATING + */ +const T_MUTATING = UNKNOWN; /** * @var int * @cvalue T_PRIVATE @@ -387,6 +392,11 @@ * @cvalue T_CLASS */ const T_CLASS = UNKNOWN; +/** + * @var int + * @cvalue T_STRUCT + */ +const T_STRUCT = UNKNOWN; /** * @var int * @cvalue T_TRAIT diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 9c488d19f1890..c1ac3f8dc7fea 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */ + * Stub hash: 44d7771285223cc0a9f48cf210980018a3ede0bf */ static void register_tokenizer_data_symbols(int module_number) { @@ -67,6 +67,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_STATIC", T_STATIC, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ABSTRACT", T_ABSTRACT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FINAL", T_FINAL, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_MUTATING", T_MUTATING, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PRIVATE", T_PRIVATE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PROTECTED", T_PROTECTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PUBLIC", T_PUBLIC, CONST_PERSISTENT); @@ -80,6 +81,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_EMPTY", T_EMPTY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_HALT_COMPILER", T_HALT_COMPILER, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CLASS", T_CLASS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_STRUCT", T_STRUCT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_PERSISTENT); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index ba18639aead82..2322c7ac6b248 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -60,6 +60,7 @@ static zend_class_entry *zend_test_class_with_method_with_parameter_attribute; static zend_class_entry *zend_test_child_class_with_method_with_parameter_attribute; static zend_class_entry *zend_test_class_with_property_attribute; static zend_class_entry *zend_test_forbid_dynamic_call; +static zend_class_entry *zend_test_box; static zend_class_entry *zend_test_ns_foo_class; static zend_class_entry *zend_test_ns_unlikely_compile_error_class; static zend_class_entry *zend_test_ns_not_unlikely_compile_error_class; @@ -1113,6 +1114,13 @@ static ZEND_METHOD(ZendTestForbidDynamicCall, callStatic) zend_forbid_dynamic_call(); } +static ZEND_METHOD(ZendTestBox, setNull) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + ZVAL_NULL(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0)); +} + static ZEND_METHOD(_ZendTestMagicCall, __call) { zend_string *name; @@ -1328,6 +1336,8 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_forbid_dynamic_call = register_class_ZendTestForbidDynamicCall(); + zend_test_box = register_class_ZendTestBox(); + zend_test_ns_foo_class = register_class_ZendTestNS_Foo(); zend_test_ns_unlikely_compile_error_class = register_class_ZendTestNS_UnlikelyCompileError(); zend_test_ns_not_unlikely_compile_error_class = register_class_ZendTestNS_NotUnlikelyCompileError(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 10272c51cad49..2c3b27a3e12e8 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -177,6 +177,13 @@ public function call(): void {} public static function callStatic(): void {} } + /** @struct */ + class ZendTestBox { + public mixed $value; + + public /* mutating */ function setNull(): void {} + } + enum ZendTestUnitEnum { case Foo; case Bar; diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 62b57223dac2a..3627a994bc7ac 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: bedc3883fbfe2491c95375beb13140e7fcfd83a5 */ + * Stub hash: b8f9832290e537afb712a6c23be442b45294a6bc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -243,6 +243,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ZendTestForbidDynamicCall_callStatic arginfo_zend_test_void_return +#define arginfo_class_ZendTestBox_setNull arginfo_zend_test_void_return + #if (PHP_VERSION_ID >= 80100) ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZendTestNS_Foo_method, 0, 0, IS_LONG, 0) #else @@ -335,6 +337,7 @@ static ZEND_METHOD(ZendTestClassWithMethodWithParameterAttribute, override); static ZEND_METHOD(ZendTestChildClassWithMethodWithParameterAttribute, override); static ZEND_METHOD(ZendTestForbidDynamicCall, call); static ZEND_METHOD(ZendTestForbidDynamicCall, callStatic); +static ZEND_METHOD(ZendTestBox, setNull); static ZEND_METHOD(ZendTestNS_Foo, method); static ZEND_METHOD(ZendTestNS_UnlikelyCompileError, method); static ZEND_METHOD(ZendTestNS_NotUnlikelyCompileError, method); @@ -538,6 +541,11 @@ static const zend_function_entry class_ZendTestForbidDynamicCall_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_ZendTestBox_methods[] = { + ZEND_ME(ZendTestBox, setNull, arginfo_class_ZendTestBox_setNull, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_ZendTestNS_Foo_methods[] = { ZEND_ME(ZendTestNS_Foo, method, arginfo_class_ZendTestNS_Foo_method, ZEND_ACC_PUBLIC) ZEND_FE_END @@ -1148,6 +1156,25 @@ static zend_class_entry *register_class_ZendTestForbidDynamicCall(void) return class_entry; } +static zend_class_entry *register_class_ZendTestBox(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ZendTestBox", class_ZendTestBox_methods); +#if (PHP_VERSION_ID >= 80400) + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_STRUCT); +#else + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_STRUCT; +#endif + + zval property_value_default_value; + ZVAL_UNDEF(&property_value_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_VALUE), &property_value_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ANY)); + + return class_entry; +} + #if (PHP_VERSION_ID >= 80100) static zend_class_entry *register_class_ZendTestUnitEnum(void) { diff --git a/ext/zend_test/tests/structs.phpt b/ext/zend_test/tests/structs.phpt new file mode 100644 index 0000000000000..0210537e5e003 --- /dev/null +++ b/ext/zend_test/tests/structs.phpt @@ -0,0 +1,36 @@ +--TEST-- +Internal structs +--EXTENSIONS-- +zend_test +--FILE-- +value = 1; +$copy = $box; +$copy->value++; +var_dump($box); +var_dump($copy); +$copy = $box; +$copy->setNull(); +var_dump($box); +var_dump($copy); + +?> +--EXPECT-- +object(ZendTestBox)#1 (1) { + ["value"]=> + int(1) +} +object(ZendTestBox)#2 (1) { + ["value"]=> + int(2) +} +object(ZendTestBox)#1 (1) { + ["value"]=> + int(1) +} +object(ZendTestBox)#2 (1) { + ["value"]=> + NULL +} From 9c25df1410d088f9a9c7dd67ee672937523a3e84 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 9 Apr 2025 13:51:34 +0200 Subject: [PATCH 2/2] Fix missing mutating flag on internal test --- build/gen_stub.php | 17 +++++++++++++++-- ext/zend_test/test.stub.php | 3 ++- ext/zend_test/test_arginfo.h | 4 ++-- ext/zend_test/tests/structs.phpt | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index c57a6f750be55..27c3ecd1fec72 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1176,6 +1176,7 @@ class FuncInfo { /** @var FramelessFunctionInfo[] */ private array $framelessFunctionInfos; private ?ExposedDocComment $exposedDocComment; + private bool $isMutating; /** * @param ArgInfo[] $args @@ -1199,7 +1200,8 @@ public function __construct( ?int $minimumPhpVersionIdCompatibility, array $attributes, array $framelessFunctionInfos, - ?ExposedDocComment $exposedDocComment + ?ExposedDocComment $exposedDocComment, + bool $isMutating, ) { $this->name = $name; $this->classFlags = $classFlags; @@ -1218,6 +1220,7 @@ public function __construct( $this->attributes = $attributes; $this->framelessFunctionInfos = $framelessFunctionInfos; $this->exposedDocComment = $exposedDocComment; + $this->isMutating = $isMutating; if ($return->tentativeReturnType && $this->isFinalMethod()) { throw new Exception("Tentative return inapplicable for final method"); } @@ -1518,6 +1521,10 @@ private function getArginfoFlagsByPhpVersions(): array $flags[] = "ZEND_ACC_DEPRECATED"; } + if ($this->isMutating) { + $flags[] = "ZEND_ACC_MUTATING"; + } + foreach ($this->attributes as $attr) { switch ($attr->class) { case "Deprecated": @@ -4344,6 +4351,7 @@ function parseFunctionLike( $docParamTypes = []; $refcount = null; $framelessFunctionInfos = []; + $isMutating = false; if ($comments) { $tags = parseDocComments($comments); @@ -4404,6 +4412,10 @@ function parseFunctionLike( case 'frameless-function': $framelessFunctionInfos[] = new FramelessFunctionInfo($tag->getValue()); break; + + case 'mutating': + $isMutating = true; + break; } } } @@ -4507,7 +4519,8 @@ function parseFunctionLike( $minimumPhpVersionIdCompatibility, AttributeInfo::createFromGroups($func->attrGroups), $framelessFunctionInfos, - ExposedDocComment::extractExposedComment($comments) + ExposedDocComment::extractExposedComment($comments), + $isMutating, ); } catch (Exception $e) { throw new Exception($name . "(): " .$e->getMessage()); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 2c3b27a3e12e8..69aee4305c23a 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -181,7 +181,8 @@ public static function callStatic(): void {} class ZendTestBox { public mixed $value; - public /* mutating */ function setNull(): void {} + /** @mutating */ + public function setNull(): void {} } enum ZendTestUnitEnum { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 3627a994bc7ac..3c0203f49c1ee 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b8f9832290e537afb712a6c23be442b45294a6bc */ + * Stub hash: d4f304fdef699a42e6734ccbaebc7f2030ce9888 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -542,7 +542,7 @@ static const zend_function_entry class_ZendTestForbidDynamicCall_methods[] = { }; static const zend_function_entry class_ZendTestBox_methods[] = { - ZEND_ME(ZendTestBox, setNull, arginfo_class_ZendTestBox_setNull, ZEND_ACC_PUBLIC) + ZEND_ME(ZendTestBox, setNull, arginfo_class_ZendTestBox_setNull, ZEND_ACC_PUBLIC|ZEND_ACC_MUTATING) ZEND_FE_END }; diff --git a/ext/zend_test/tests/structs.phpt b/ext/zend_test/tests/structs.phpt index 0210537e5e003..8b2ca0d542549 100644 --- a/ext/zend_test/tests/structs.phpt +++ b/ext/zend_test/tests/structs.phpt @@ -12,7 +12,7 @@ $copy->value++; var_dump($box); var_dump($copy); $copy = $box; -$copy->setNull(); +$copy->setNull!(); var_dump($box); var_dump($copy);