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 0881a169dfa7d..727555519edb0 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -5177,6 +5177,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 9774cce39db2b..550d3ccdb84cd 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2739,9 +2739,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 fb48b187252b3..8b9c700215258 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 5eba2ec1366fa..08d8405fe177d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -873,6 +873,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() } } @@ -927,6 +929,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; @@ -997,6 +1004,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; } /* }}} */ @@ -2491,6 +2508,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: @@ -2505,6 +2523,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: @@ -2727,7 +2746,9 @@ static inline bool zend_is_call(const zend_ast *ast) /* {{{ */ || ast->kind == ZEND_AST_METHOD_CALL || ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL || ast->kind == ZEND_AST_STATIC_CALL - || ast->kind == ZEND_AST_PIPE; + || ast->kind == ZEND_AST_PIPE + || ast->kind == ZEND_AST_MUTATING_METHOD_CALL + || ast->kind == ZEND_AST_STATIC_CALL; } /* }}} */ @@ -3389,6 +3410,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"); @@ -3498,7 +3520,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; @@ -3971,7 +3993,7 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze 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); @@ -5294,6 +5316,7 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type zend_op *opline; const 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)) { @@ -5308,7 +5331,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); } @@ -5316,6 +5343,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) { @@ -8572,6 +8602,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) { @@ -11918,6 +11951,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, false); @@ -12052,6 +12086,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. */ @@ -12077,6 +12112,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 d2a3b47bf92f4..8ba305eaa80f8 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -275,7 +275,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: 31) | | | */ +/* Class Flags (unused: none) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -341,12 +341,15 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ +/* Structs are value types | | | */ +#define ZEND_ACC_STRUCT (1U << 31) /* X | | | */ +/* | | | */ /* Class Flags 2 (ce_flags2) (unused: 0-31) | | | */ /* ========================= | | | */ /* | | | */ /* #define ZEND_ACC2_EXAMPLE (1 << 0) X | | | */ /* | | | */ -/* Function Flags (unused: 30) | | | */ +/* Function Flags (unused: none) | | | */ /* ============== | | | */ /* | | | */ /* Function returning by reference | | | */ @@ -410,6 +413,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 | | */ /* | | | */ @@ -1127,6 +1133,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 518cbb98fc0f8..c91911697c7de 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3543,6 +3543,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))) { @@ -3563,7 +3567,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 e4d61006fe12f..a8850a0e196c2 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -152,6 +152,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'" @@ -165,6 +166,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'" @@ -292,6 +294,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 @@ -310,14 +313,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: @@ -325,6 +328,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 { @@ -602,12 +610,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: @@ -1112,6 +1120,7 @@ member_modifier: | T_ABSTRACT { $$ = T_ABSTRACT; } | T_FINAL { $$ = T_FINAL; } | T_READONLY { $$ = T_READONLY; } + | T_MUTATING { $$ = T_MUTATING; } ; property_list: @@ -1571,6 +1580,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 1e26ddbd99199..6c442081110cb 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1576,6 +1576,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); } @@ -1775,6 +1785,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 88b7b1112d7b1..65a1f060a4e40 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -759,7 +759,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 2550fcbeb1cde..a5fb7660e4840 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -2476,6 +2476,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)) { @@ -2498,7 +2553,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 43aa2aa86a00e..09d125976b237 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 1b91f11662c7a..b45ef28c992b6 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))) { @@ -3588,10 +3600,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); @@ -3608,13 +3631,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); } @@ -3624,6 +3651,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))) { @@ -3631,8 +3661,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 { @@ -3656,7 +3689,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); @@ -3686,6 +3721,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)) { @@ -3718,7 +3768,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) */ @@ -3726,7 +3776,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; @@ -6991,6 +7041,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))) { @@ -9222,8 +9275,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 801bf0ee69e0d..d48ad1ea0b1cd 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5651,6 +5651,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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))) { @@ -7306,10 +7309,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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); @@ -7326,16 +7340,20 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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); } @@ -7345,6 +7363,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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))) { @@ -7352,8 +7373,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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 { @@ -7379,8 +7403,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -7411,6 +7437,22 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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)) { @@ -7444,7 +7486,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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) */ @@ -7452,7 +7494,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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; @@ -10021,10 +10063,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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); @@ -10041,15 +10094,19 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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); } @@ -10059,6 +10116,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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))) { @@ -10066,8 +10126,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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 { @@ -10091,8 +10154,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -10122,6 +10187,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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)) { @@ -10154,7 +10234,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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) */ @@ -10162,7 +10242,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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; @@ -12593,10 +12673,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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); @@ -12613,16 +12704,20 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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); } @@ -12632,6 +12727,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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))) { @@ -12639,8 +12737,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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 { @@ -12666,8 +12767,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -12698,6 +12801,22 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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)) { @@ -12731,7 +12850,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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) */ @@ -12739,7 +12858,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ } 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; @@ -17139,10 +17258,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); @@ -17159,14 +17289,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); } @@ -17176,6 +17310,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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))) { @@ -17183,8 +17320,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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 { @@ -17210,7 +17350,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); @@ -17241,6 +17383,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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)) { @@ -17274,7 +17432,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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) */ @@ -17282,7 +17440,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -18644,10 +18802,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -18664,13 +18833,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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); } @@ -18680,6 +18853,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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))) { @@ -18687,8 +18863,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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 { @@ -18712,7 +18891,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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); @@ -18742,6 +18923,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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"); + } + 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)) { @@ -18774,7 +18970,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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) */ @@ -18782,7 +18978,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -20062,10 +20258,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); @@ -20082,14 +20289,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); } @@ -20099,6 +20310,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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))) { @@ -20106,8 +20320,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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 { @@ -20133,7 +20350,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); @@ -20164,6 +20383,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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)) { @@ -20197,7 +20432,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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) */ @@ -20205,7 +20440,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -20815,6 +21050,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FE_RESET_RW_S 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))) { @@ -23531,6 +23769,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FE_RESET_RW_S 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))) { @@ -24142,6 +24383,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -24365,6 +24609,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -24430,6 +24677,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -24649,6 +24899,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -24806,6 +25059,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -24961,6 +25217,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -25116,6 +25375,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -27224,6 +27486,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -27446,6 +27711,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -27511,6 +27779,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -27724,6 +27995,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -27880,6 +28154,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -28034,6 +28311,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -28188,6 +28468,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -31661,6 +31944,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -31884,6 +32170,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -31949,6 +32238,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -32168,6 +32460,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -32325,6 +32620,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -32480,6 +32778,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -32635,6 +32936,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -34409,6 +34713,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -34501,6 +34808,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -34567,6 +34877,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -34997,6 +35310,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -35155,6 +35471,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -35311,6 +35630,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -35467,6 +35789,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -35768,10 +36093,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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); @@ -35788,16 +36124,20 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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); } @@ -35807,6 +36147,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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))) { @@ -35814,8 +36157,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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 { @@ -35841,8 +36187,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -35873,6 +36221,22 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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)) { @@ -35906,7 +36270,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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) */ @@ -35914,7 +36278,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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; @@ -36660,6 +37024,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -36751,6 +37118,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -36817,6 +37187,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -37237,6 +37610,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -37394,6 +37770,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -37549,6 +37928,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -37704,6 +38086,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -38001,10 +38386,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -38021,15 +38417,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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); } @@ -38039,6 +38439,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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))) { @@ -38046,8 +38449,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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 { @@ -38071,8 +38477,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -38102,6 +38510,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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"); + } + 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)) { @@ -38134,7 +38557,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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) */ @@ -38142,7 +38565,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -39076,8 +39499,14 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_F 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)); @@ -39364,6 +39793,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -39456,6 +39888,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -39522,6 +39957,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -39947,6 +40385,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -40105,6 +40546,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -40261,6 +40705,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -40417,6 +40864,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -40718,10 +41168,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); @@ -40738,16 +41199,20 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); } @@ -40757,6 +41222,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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))) { @@ -40764,8 +41232,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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 { @@ -40791,8 +41262,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -40823,6 +41296,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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)) { @@ -40856,7 +41345,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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) */ @@ -40864,7 +41353,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -42293,6 +42782,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FE_RESET_RW_S 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))) { @@ -43605,6 +44097,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -43831,6 +44326,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -43897,6 +44395,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -44446,6 +44947,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -44604,6 +45108,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -44760,6 +45267,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -44916,6 +45426,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -45953,10 +46466,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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); @@ -45973,16 +46497,20 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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); } @@ -45992,6 +46520,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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))) { @@ -45999,8 +46530,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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 { @@ -46026,8 +46560,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -46058,6 +46594,22 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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)) { @@ -46091,7 +46643,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I 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) */ @@ -46099,7 +46651,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I } 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; @@ -47697,6 +48249,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -47922,6 +48477,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -47988,6 +48546,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -48522,6 +49083,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -48679,6 +49243,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -48834,6 +49401,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -48989,6 +49559,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -49958,10 +50531,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C 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); @@ -49978,15 +50562,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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); } @@ -49996,6 +50584,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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))) { @@ -50003,8 +50594,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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_CV & IS_VAR) { + if ((IS_CV & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -50028,8 +50622,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -50059,6 +50655,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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"); + } + 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)) { @@ -50091,7 +50702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C 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) */ @@ -50099,7 +50710,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -53356,6 +53967,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP 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); @@ -53582,6 +54196,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S 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); @@ -53648,6 +54265,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ 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); @@ -54192,6 +54812,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -54350,6 +54973,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -54506,6 +55132,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -54662,6 +55291,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } 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))) { @@ -55739,10 +56371,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C 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); @@ -55759,16 +56402,20 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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); } @@ -55778,6 +56425,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C } 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))) { @@ -55785,8 +56435,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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_CV & IS_VAR) { + if ((IS_CV & IS_VAR) && !needs_addref) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { efree_size(ref, sizeof(zend_reference)); } else { @@ -55812,8 +56465,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -55844,6 +56499,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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_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)) { @@ -55877,7 +56548,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_C 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) */ @@ -55885,7 +56556,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_METHOD_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; @@ -61319,6 +61990,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FE_RE 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))) { @@ -62974,10 +63648,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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); @@ -62994,16 +63679,20 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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); } @@ -63013,6 +63702,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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))) { @@ -63020,8 +63712,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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 { @@ -63047,8 +63742,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -63079,6 +63776,22 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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)) { @@ -63112,7 +63825,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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) */ @@ -63120,7 +63833,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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; @@ -65689,10 +66402,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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); @@ -65709,15 +66433,19 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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); } @@ -65727,6 +66455,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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))) { @@ -65734,8 +66465,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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 { @@ -65759,8 +66493,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -65790,6 +66526,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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)) { @@ -65822,7 +66573,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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) */ @@ -65830,7 +66581,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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; @@ -68159,10 +68910,21 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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); @@ -68179,16 +68941,20 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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); } @@ -68198,6 +68964,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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))) { @@ -68205,8 +68974,11 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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 { @@ -68232,8 +69004,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -68264,6 +69038,22 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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)) { @@ -68297,7 +69087,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ 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) */ @@ -68305,7 +69095,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_ } 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; @@ -72705,10 +73495,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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); @@ -72725,14 +73526,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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)) { - 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); } @@ -72742,6 +73547,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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))) { @@ -72749,8 +73557,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_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 { @@ -72776,7 +73587,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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); @@ -72807,6 +73620,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_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)) { @@ -72840,7 +73669,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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) */ @@ -72848,7 +73677,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -74210,10 +75039,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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); @@ -74230,13 +75070,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -74246,6 +75090,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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))) { @@ -74253,8 +75100,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_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 { @@ -74278,7 +75128,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); @@ -74308,6 +75160,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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"); + } + 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)) { @@ -74340,7 +75207,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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) */ @@ -74348,7 +75215,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -75528,10 +76395,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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); @@ -75548,14 +76426,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -75565,6 +76447,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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))) { @@ -75572,8 +76457,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_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 { @@ -75599,7 +76487,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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); @@ -75630,6 +76520,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_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)) { @@ -75663,7 +76569,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S 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) */ @@ -75671,7 +76577,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -76281,6 +77187,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FE_RESET_RW_SPEC_T 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))) { @@ -78997,6 +79906,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FE_RESET_RW_SPEC_V 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))) { @@ -79608,6 +80520,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -79831,6 +80746,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V 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); @@ -79896,6 +80814,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -80115,6 +81036,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -80272,6 +81196,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -80427,6 +81354,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -80582,6 +81512,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -82690,6 +83623,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -82912,6 +83848,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V 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); @@ -82977,6 +83916,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -83190,6 +84132,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -83346,6 +84291,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -83500,6 +84448,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -83654,6 +84605,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -87127,6 +88081,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -87350,6 +88307,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V 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); @@ -87415,6 +88375,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -87634,6 +88597,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -87791,6 +88757,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -87946,6 +88915,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -88101,6 +89073,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } 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))) { @@ -89875,6 +90850,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -89967,6 +90945,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U 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); @@ -90033,6 +91014,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -90463,6 +91447,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -90621,6 +91608,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -90777,6 +91767,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -90933,6 +91926,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -91234,10 +92230,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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); @@ -91254,16 +92261,20 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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); } @@ -91273,6 +92284,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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))) { @@ -91280,8 +92294,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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 { @@ -91307,8 +92324,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -91339,6 +92358,22 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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)) { @@ -91372,7 +92407,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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) */ @@ -91380,7 +92415,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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; @@ -92126,6 +93161,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -92217,6 +93255,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U 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); @@ -92283,6 +93324,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -92703,6 +93747,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -92860,6 +93907,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -93015,6 +94065,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -93170,6 +94223,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -93467,10 +94523,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -93487,15 +94554,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -93505,6 +94576,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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))) { @@ -93512,8 +94586,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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 { @@ -93537,8 +94614,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -93568,6 +94647,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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"); + } + 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)) { @@ -93600,7 +94694,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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) */ @@ -93608,7 +94702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -94542,8 +95636,14 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FETCH_ 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_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); @@ -94830,6 +95930,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -94922,6 +96025,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U 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); @@ -94988,6 +96094,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -95413,6 +96522,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -95571,6 +96683,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -95727,6 +96842,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -95883,6 +97001,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } 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))) { @@ -96184,10 +97305,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -96204,16 +97336,20 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -96223,6 +97359,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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))) { @@ -96230,8 +97369,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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 { @@ -96257,8 +97399,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -96289,6 +97433,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -96322,7 +97482,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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) */ @@ -96330,7 +97490,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -97759,6 +98919,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FE_RESET_RW_SPEC_C 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))) { @@ -99071,6 +100234,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -99297,6 +100463,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C 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); @@ -99363,6 +100532,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -99912,6 +101084,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -100070,6 +101245,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -100226,6 +101404,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -100382,6 +101563,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -101419,10 +102603,21 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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); @@ -101439,16 +102634,20 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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); } @@ -101458,6 +102657,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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))) { @@ -101465,8 +102667,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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 { @@ -101492,8 +102697,10 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -101524,6 +102731,22 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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)) { @@ -101557,7 +102780,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M 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) */ @@ -101565,7 +102788,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_M } 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; @@ -103163,6 +104386,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -103388,6 +104614,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C 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); @@ -103454,6 +104683,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -103988,6 +105220,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -104145,6 +105380,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -104300,6 +105538,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -104455,6 +105696,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -105424,10 +106668,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_TMP_VAR|IS_VAR) != IS_CONST) { function_name = _get_zval_ptr_var(opline->op2.var EXECUTE_DATA_CC); @@ -105444,15 +106699,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -105462,6 +106721,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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))) { @@ -105469,8 +106731,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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 { @@ -105494,8 +106759,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } zend_invalid_method_call(object, function_name); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -105525,6 +106792,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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"); + } + 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)) { @@ -105557,7 +106839,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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) */ @@ -105565,7 +106847,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; @@ -108720,6 +110002,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC 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); @@ -108946,6 +110231,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C 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); @@ -109012,6 +110300,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ 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); @@ -109556,6 +110847,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -109714,6 +111008,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -109870,6 +111167,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -110026,6 +111326,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } 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))) { @@ -111103,10 +112406,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_CV != IS_CONST) { function_name = EX_VAR(opline->op2.var); @@ -111123,16 +112437,20 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S } 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); } @@ -111142,6 +112460,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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))) { @@ -111149,8 +112470,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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 { @@ -111176,8 +112500,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_METHOD_CALL_S zend_invalid_method_call(object, function_name); + if (!needs_addref) { + } HANDLE_EXCEPTION(); } } while (0); @@ -111208,6 +112534,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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_CV == IS_CONST && EXPECTED(!(fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE|ZEND_ACC_NEVER_CACHE))) && EXPECTED(obj == orig_obj)) { @@ -111241,7 +112583,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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) */ @@ -111249,7 +112591,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV 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; diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 8c1263885bf6c..063ab6ed249b9 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -354,6 +354,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) { @@ -362,8 +378,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; } @@ -400,8 +415,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; } @@ -430,8 +444,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; } @@ -450,8 +463,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 acee1e6ca0981..c68893e9b35ab 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1320,6 +1320,7 @@ class FuncInfo { /** @var FramelessFunctionInfo[] */ private array $framelessFunctionInfos; private ?ExposedDocComment $exposedDocComment; + private bool $isMutating; /** * @param ArgInfo[] $args @@ -1343,7 +1344,8 @@ public function __construct( ?int $minimumPhpVersionIdCompatibility, array $attributes, array $framelessFunctionInfos, - ?ExposedDocComment $exposedDocComment + ?ExposedDocComment $exposedDocComment, + bool $isMutating, ) { $this->name = $name; $this->classFlags = $classFlags; @@ -1362,6 +1364,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"); } @@ -1641,6 +1644,10 @@ private function getArginfoFlagsByPhpVersions(): VersionFlags $flags[] = "ZEND_ACC_DEPRECATED"; } + if ($this->isMutating) { + $flags[] = "ZEND_ACC_MUTATING"; + } + foreach ($this->attributes as $attr) { switch ($attr->class) { case "Deprecated": @@ -2215,11 +2222,11 @@ public function toArgInfoCode(?int $minPHPCompatability): string { $this->numRequiredArgs, $minPHPCompatability === null || $minPHPCompatability >= PHP_81_VERSION_ID ); - + foreach ($this->args as $argInfo) { $code .= $argInfo->toZendInfo(); } - + $code .= "ZEND_END_ARG_INFO()"; return $code . "\n"; } @@ -3410,6 +3417,7 @@ class ClassInfo { private array $attributes; private ?ExposedDocComment $exposedDocComment; private bool $isNotSerializable; + private bool $isStruct; /** @var Name[] */ private /* readonly */ array $extends; /** @var Name[] */ @@ -3446,6 +3454,7 @@ public function __construct( array $attributes, ?ExposedDocComment $exposedDocComment, bool $isNotSerializable, + bool $isStruct, array $extends, array $implements, array $constInfos, @@ -3466,6 +3475,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; @@ -3681,6 +3691,12 @@ private function getFlagsByPhpVersion(): VersionFlags $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"; + } + $flags = new VersionFlags($php70Flags); if ($this->isStrictProperties) { @@ -3711,6 +3727,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); @@ -4310,13 +4327,13 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { return implode('\\', $node->getParts()); } }; - + $stmts = $parser->parse($code); $nodeTraverser->traverse($stmts); - + $fileTags = DocCommentTag::parseDocComments(self::getFileDocComments($stmts)); $fileInfo = new FileInfo($fileTags); - + $fileInfo->handleStatements($stmts, $prettyPrinter); return $fileInfo; } @@ -4337,16 +4354,16 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri $conds = []; foreach ($stmts as $stmt) { $cond = self::handlePreprocessorConditions($conds, $stmt); - + if ($stmt instanceof Stmt\Nop) { continue; } - + if ($stmt instanceof Stmt\Namespace_) { $this->handleStatements($stmt->stmts, $prettyPrinter); continue; } - + if ($stmt instanceof Stmt\Const_) { foreach ($stmt->consts as $const) { $this->constInfos[] = parseConstLike( @@ -4364,7 +4381,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri } continue; } - + if ($stmt instanceof Stmt\Function_) { $this->funcInfos[] = parseFunctionLike( $prettyPrinter, @@ -4378,7 +4395,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri ); continue; } - + if ($stmt instanceof Stmt\ClassLike) { $className = $stmt->namespacedName; $constInfos = []; @@ -4390,10 +4407,10 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri if ($classStmt instanceof Stmt\Nop) { continue; } - + $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; - + if ($classStmt instanceof Stmt\ClassConst) { foreach ($classStmt->consts as $const) { $constInfos[] = parseConstLike( @@ -4447,7 +4464,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri throw new Exception("Not implemented {$classStmt->getType()}"); } } - + $this->classInfos[] = parseClass( $className, $stmt, @@ -4461,7 +4478,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri ); continue; } - + if ($stmt instanceof Stmt\Expression) { $expr = $stmt->expr; if ($expr instanceof Expr\Include_) { @@ -4469,7 +4486,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri continue; } } - + throw new Exception("Unexpected node {$stmt->getType()}"); } if (!empty($conds)) { @@ -4501,7 +4518,7 @@ private static function handlePreprocessorConditions(array &$conds, Stmt $stmt): throw new Exception("Unrecognized preprocessor directive \"$text\""); } } - + return empty($conds) ? null : implode(' && ', $conds); } @@ -4684,6 +4701,7 @@ function parseFunctionLike( $docParamTypes = []; $refcount = null; $framelessFunctionInfos = []; + $isMutating = false; if ($comments) { $tags = DocCommentTag::parseDocComments($comments); @@ -4731,6 +4749,10 @@ function parseFunctionLike( case 'frameless-function': $framelessFunctionInfos[] = new FramelessFunctionInfo($tag->getValue()); break; + + case 'mutating': + $isMutating = true; + break; } } } @@ -4834,7 +4856,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()); @@ -5023,6 +5046,7 @@ function parseClass( $isStrictProperties = array_key_exists('strict-properties', $tagMap); $isNotSerializable = array_key_exists('not-serializable', $tagMap); $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); + $isStruct = array_key_exists('struct', $tagMap); foreach ($tags as $tag) { if ($tag->name === 'alias') { $alias = $tag->getValue(); @@ -5081,6 +5105,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 c6f681ee2276c..0c71db9e7957d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3441,6 +3441,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); @@ -5959,6 +5966,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(); + } + const 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 3d6870a7ee953..ff2ba180b2c32 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -1011,9 +1011,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 801c091fbb42d..b171c58adfb43 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -80,6 +80,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); @@ -505,6 +543,14 @@ static void spl_multiple_iterator_write_dimension(zend_object *object, zval *off 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); @@ -522,6 +568,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); @@ -537,6 +588,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)); } /* }}} */ @@ -553,6 +608,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(); } @@ -648,6 +707,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 0900c51d3d95a..adbabd0deb048 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 57c8edad8acb6..3d9315944ea40 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 3a3cdaa468133..4a3216e980e89 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: c5235344b7c651d27c2c33c90696a418a9c96837 */ + * Stub hash: 66661b8d80c8f0154edb8f425fc60ac8f5235b44 */ 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 fd1a51c5776c3..e23040bd021c3 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -66,6 +66,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; @@ -1347,6 +1348,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; @@ -1567,6 +1575,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 d0c0c64b8b1d0..f1234c10ccc63 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -190,6 +190,14 @@ public function call(): void {} public static function callStatic(): void {} } + /** @struct */ + class ZendTestBox { + public mixed $value; + + /** @mutating */ + public 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 683b3b38648b6..db676794820cb 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: a8dae89983ccbcd5dd36d1cdee736d40af4fd33c */ + * Stub hash: 5a8d8db185714849ece3234fa9749a2c98033bb8 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) ZEND_END_ARG_INFO() @@ -267,6 +267,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 @@ -367,6 +369,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); @@ -594,6 +597,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_ACC_MUTATING) + 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 @@ -1202,6 +1210,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..8b2ca0d542549 --- /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 +}