diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 695c4c166a83c..9f8e9b77003bc 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -183,7 +183,7 @@ assert(0 && ($a = function () { $x = $a ?? $b; [$a, $b, $c] = [1, 2 => 'x', 'z' => 'c']; @foo(); - $y = clone $x; + $y = \clone($x); yield 1 => 2; yield from $x; })) diff --git a/Zend/tests/clone/ast.phpt b/Zend/tests/clone/ast.phpt new file mode 100644 index 0000000000000..13d94eb91d26c --- /dev/null +++ b/Zend/tests/clone/ast.phpt @@ -0,0 +1,87 @@ +--TEST-- +Ast Printing +--FILE-- +getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone($x)); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone($x, [ "foo" => $foo, "bar" => $bar ])); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone($x, $array)); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone($x, $array, $extraParameter, $trailingComma, )); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone(object: $x, withProperties: [ "foo" => $foo, "bar" => $bar ])); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone($x, withProperties: [ "foo" => $foo, "bar" => $bar ])); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone(object: $x)); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone(object: $x, [ "foo" => $foo, "bar" => $bar ])); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone(...["object" => $x, "withProperties" => [ "foo" => $foo, "bar" => $bar ]])); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(false && $y = clone(...)); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +assert(false && ($y = \clone($x))) +assert(false && ($y = \clone($x))) +assert(false && ($y = \clone($x, ['foo' => $foo, 'bar' => $bar]))) +assert(false && ($y = \clone($x, $array))) +assert(false && ($y = \clone($x, $array, $extraParameter, $trailingComma))) +assert(false && ($y = \clone(object: $x, withProperties: ['foo' => $foo, 'bar' => $bar]))) +assert(false && ($y = \clone($x, withProperties: ['foo' => $foo, 'bar' => $bar]))) +assert(false && ($y = \clone(object: $x))) +assert(false && ($y = \clone(object: $x, ['foo' => $foo, 'bar' => $bar]))) +assert(false && ($y = \clone(...['object' => $x, 'withProperties' => ['foo' => $foo, 'bar' => $bar]]))) +assert(false && ($y = \clone(...))) diff --git a/Zend/tests/clone/bug36071.phpt b/Zend/tests/clone/bug36071.phpt index 945118fef3754..d9b3bc9cbe1f2 100644 --- a/Zend/tests/clone/bug36071.phpt +++ b/Zend/tests/clone/bug36071.phpt @@ -8,7 +8,8 @@ $a = clone 0; $a[0]->b = 0; ?> --EXPECTF-- -Fatal error: Uncaught Error: __clone method called on non-object in %sbug36071.php:2 +Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, int given in %s:%d Stack trace: -#0 {main} +#0 %s(%d): clone(0) +#1 {main} thrown in %sbug36071.php on line 2 diff --git a/Zend/tests/clone/bug42817.phpt b/Zend/tests/clone/bug42817.phpt index a681d861d0c8f..635e8f2a608d0 100644 --- a/Zend/tests/clone/bug42817.phpt +++ b/Zend/tests/clone/bug42817.phpt @@ -6,7 +6,8 @@ $a = clone(null); array_push($a->b, $c); ?> --EXPECTF-- -Fatal error: Uncaught Error: __clone method called on non-object in %sbug42817.php:2 +Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, null given in %s:%d Stack trace: -#0 {main} +#0 %s(%d): clone(NULL) +#1 {main} thrown in %sbug42817.php on line 2 diff --git a/Zend/tests/clone/bug42818.phpt b/Zend/tests/clone/bug42818.phpt index b37ce13fd174a..9cd7050d55b97 100644 --- a/Zend/tests/clone/bug42818.phpt +++ b/Zend/tests/clone/bug42818.phpt @@ -5,7 +5,8 @@ Bug #42818 ($foo = clone(array()); leaks memory) $foo = clone(array()); ?> --EXPECTF-- -Fatal error: Uncaught Error: __clone method called on non-object in %sbug42818.php:2 +Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, array given in %s:%d Stack trace: -#0 {main} +#0 %s(%d): clone(Array) +#1 {main} thrown in %sbug42818.php on line 2 diff --git a/Zend/tests/clone/callback.phpt b/Zend/tests/clone/callback.phpt new file mode 100644 index 0000000000000..30ce6a93bc59f --- /dev/null +++ b/Zend/tests/clone/callback.phpt @@ -0,0 +1,24 @@ +--TEST-- +As Callback +--FILE-- +clone_me()[0]; + +var_dump($f !== $clone); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/clone/clone_001.phpt b/Zend/tests/clone/clone_001.phpt index 87024c3cd5614..dbbb16049b80b 100644 --- a/Zend/tests/clone/clone_001.phpt +++ b/Zend/tests/clone/clone_001.phpt @@ -7,7 +7,8 @@ $a = clone array(); ?> --EXPECTF-- -Fatal error: Uncaught Error: __clone method called on non-object in %s:%d +Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, array given in %s:%d Stack trace: -#0 {main} +#0 %s(%d): clone(Array) +#1 {main} thrown in %s on line %d diff --git a/Zend/tests/clone/clone_003.phpt b/Zend/tests/clone/clone_003.phpt index f163616a876dc..19f775f1e9ff3 100644 --- a/Zend/tests/clone/clone_003.phpt +++ b/Zend/tests/clone/clone_003.phpt @@ -9,7 +9,8 @@ $a = clone $b; --EXPECTF-- Warning: Undefined variable $b in %s on line %d -Fatal error: Uncaught Error: __clone method called on non-object in %s:%d +Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, null given in %s:%d Stack trace: -#0 {main} +#0 %s(%d): clone(NULL) +#1 {main} thrown in %s on line %d diff --git a/Zend/tests/clone/clone_with_001.phpt b/Zend/tests/clone/clone_with_001.phpt new file mode 100644 index 0000000000000..ca6abd53a72eb --- /dev/null +++ b/Zend/tests/clone/clone_with_001.phpt @@ -0,0 +1,75 @@ +--TEST-- +Clone with basic +--FILE-- + 'BAZ', + 'array' => [1, 2, 3], +]; + +function gen() { + yield 'from_gen' => 'value'; +} + +var_dump(clone $x); +var_dump(clone($x)); +var_dump(clone($x, [ 'foo' => $foo, 'bar' => $bar ])); +var_dump(clone($x, $array)); +var_dump(clone($x, [ 'obj' => $x ])); + +var_dump(clone($x, [ + 'abc', + 'def', + new Dummy(), + 'named' => 'value', +])); + +?> +--EXPECTF-- +object(stdClass)#%d (0) { +} +object(stdClass)#%d (0) { +} +object(stdClass)#%d (2) { + ["foo"]=> + string(3) "FOO" + ["bar"]=> + object(Dummy)#%d (0) { + } +} +object(stdClass)#%d (2) { + ["baz"]=> + string(3) "BAZ" + ["array"]=> + array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + } +} +object(stdClass)#%d (1) { + ["obj"]=> + object(stdClass)#%d (0) { + } +} +object(stdClass)#%d (4) { + ["0"]=> + string(3) "abc" + ["1"]=> + string(3) "def" + ["2"]=> + object(Dummy)#%d (0) { + } + ["named"]=> + string(5) "value" +} diff --git a/Zend/tests/clone/clone_with_002.phpt b/Zend/tests/clone/clone_with_002.phpt new file mode 100644 index 0000000000000..8b86e64c76aa8 --- /dev/null +++ b/Zend/tests/clone/clone_with_002.phpt @@ -0,0 +1,114 @@ +--TEST-- +Clone with respects visiblity +--FILE-- + 'updated A', 'b' => 'updated B', 'c' => 'updated C', 'd' => 'updated D' ]); + } +} + +class C extends P { + public function m2() { + return clone($this, [ 'a' => 'updated A', 'b' => 'updated B', 'c' => 'dynamic C' ]); + } + + public function m3() { + return clone($this, [ 'd' => 'inaccessible' ]); + } +} + +class Unrelated { + public function m3(P $p) { + return clone($p, [ 'b' => 'inaccessible' ]); + } +} + +$p = new P(); + +var_dump(clone($p, [ 'a' => 'updated A' ])); +var_dump($p->m1()); + +$c = new C(); +var_dump($c->m1()); +var_dump($c->m2()); +try { + var_dump($c->m3()); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +try { + var_dump(clone($p, [ 'b' => 'inaccessible' ])); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +try { + var_dump(clone($p, [ 'd' => 'inaccessible' ])); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +try { + var_dump((new Unrelated())->m3($p)); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +object(P)#%d (4) { + ["a"]=> + string(9) "updated A" + ["b":protected]=> + string(7) "default" + ["c":"P":private]=> + string(7) "default" + ["d"]=> + string(7) "default" +} +object(P)#%d (4) { + ["a"]=> + string(9) "updated A" + ["b":protected]=> + string(9) "updated B" + ["c":"P":private]=> + string(9) "updated C" + ["d"]=> + string(9) "updated D" +} +object(C)#%d (4) { + ["a"]=> + string(9) "updated A" + ["b":protected]=> + string(9) "updated B" + ["c":"P":private]=> + string(9) "updated C" + ["d"]=> + string(9) "updated D" +} + +Deprecated: Creation of dynamic property C::$c is deprecated in %s on line %d +object(C)#%d (5) { + ["a"]=> + string(9) "updated A" + ["b":protected]=> + string(9) "updated B" + ["c":"P":private]=> + string(7) "default" + ["d"]=> + string(7) "default" + ["c"]=> + string(9) "dynamic C" +} +Error: Cannot modify private(set) property P::$d from scope C +Error: Cannot access protected property P::$b +Error: Cannot modify private(set) property P::$d from global scope +Error: Cannot access protected property P::$b diff --git a/Zend/tests/clone/clone_with_003.phpt b/Zend/tests/clone/clone_with_003.phpt new file mode 100644 index 0000000000000..fa7ece0fe04fa --- /dev/null +++ b/Zend/tests/clone/clone_with_003.phpt @@ -0,0 +1,23 @@ +--TEST-- +Clone with supports property hooks +--FILE-- +hooked = strtoupper($value); + } + } +} + +$c = new Clazz(); + +var_dump(clone($c, [ 'hooked' => 'updated' ])); + +?> +--EXPECTF-- +object(Clazz)#%d (1) { + ["hooked"]=> + string(7) "UPDATED" +} diff --git a/Zend/tests/clone/clone_with_004.phpt b/Zend/tests/clone/clone_with_004.phpt new file mode 100644 index 0000000000000..14d01bb75fa5b --- /dev/null +++ b/Zend/tests/clone/clone_with_004.phpt @@ -0,0 +1,82 @@ +--TEST-- +Clone with evaluation order +--FILE-- +hooked = strtoupper($value); + } + } + + public string $maxLength { + set (string $value) { + echo __FUNCTION__, PHP_EOL; + + if (strlen($value) > 5) { + throw new \Exception('Length exceeded'); + } + + $this->maxLength = $value; + } + } + + public string $minLength { + set (string $value) { + echo __FUNCTION__, PHP_EOL; + + if (strlen($value) < 5) { + throw new \Exception('Length unsufficient'); + } + + $this->minLength = $value; + } + } +} + +$c = new Clazz(); + +var_dump(clone($c, [ 'hooked' => 'updated' ])); +echo PHP_EOL; +var_dump(clone($c, [ 'hooked' => 'updated', 'maxLength' => 'abc', 'minLength' => 'abcdef' ])); +echo PHP_EOL; +var_dump(clone($c, [ 'minLength' => 'abcdef', 'hooked' => 'updated', 'maxLength' => 'abc' ])); + +?> +--EXPECTF-- +$hooked::set +object(Clazz)#%d (1) { + ["hooked"]=> + string(7) "UPDATED" + ["maxLength"]=> + uninitialized(string) + ["minLength"]=> + uninitialized(string) +} + +$hooked::set +$maxLength::set +$minLength::set +object(Clazz)#%d (3) { + ["hooked"]=> + string(7) "UPDATED" + ["maxLength"]=> + string(3) "abc" + ["minLength"]=> + string(6) "abcdef" +} + +$minLength::set +$hooked::set +$maxLength::set +object(Clazz)#%d (3) { + ["hooked"]=> + string(7) "UPDATED" + ["maxLength"]=> + string(3) "abc" + ["minLength"]=> + string(6) "abcdef" +} diff --git a/Zend/tests/clone/clone_with_005.phpt b/Zend/tests/clone/clone_with_005.phpt new file mode 100644 index 0000000000000..40569c59192ad --- /dev/null +++ b/Zend/tests/clone/clone_with_005.phpt @@ -0,0 +1,64 @@ +--TEST-- +Clone with error handling +--FILE-- +hooked = strtoupper($value); + } + } + + public string $maxLength { + set (string $value) { + echo __FUNCTION__, PHP_EOL; + + if (strlen($value) > 5) { + throw new \Exception('Length exceeded'); + } + + $this->maxLength = $value; + } + } + + public string $minLength { + set (string $value) { + echo __FUNCTION__, PHP_EOL; + + if (strlen($value) < 5) { + throw new \Exception('Length insufficient'); + } + + $this->minLength = $value; + } + } +} + +$c = new Clazz(); + +try { + var_dump(clone($c, [ 'hooked' => 'updated', 'maxLength' => 'abcdef', 'minLength' => 'abc' ])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +echo PHP_EOL; + +try { + var_dump(clone($c, [ 'hooked' => 'updated', 'minLength' => 'abc', 'maxLength' => 'abcdef' ])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +$hooked::set +$maxLength::set +Exception: Length exceeded + +$hooked::set +$minLength::set +Exception: Length insufficient diff --git a/Zend/tests/clone/clone_with_006.phpt b/Zend/tests/clone/clone_with_006.phpt new file mode 100644 index 0000000000000..7b0b8520b8a82 --- /dev/null +++ b/Zend/tests/clone/clone_with_006.phpt @@ -0,0 +1,16 @@ +--TEST-- +Clone with error cases +--FILE-- +getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +TypeError: clone(): Argument #2 ($withProperties) must be of type array, int given diff --git a/Zend/tests/clone/clone_with_007.phpt b/Zend/tests/clone/clone_with_007.phpt new file mode 100644 index 0000000000000..08cefd7f8cbe3 --- /dev/null +++ b/Zend/tests/clone/clone_with_007.phpt @@ -0,0 +1,29 @@ +--TEST-- +Clone with supports __clone +--FILE-- +foo = 'foo updated in __clone'; + $this->bar = 'bar updated in __clone'; + } +} + +$c = new Clazz('foo', 'bar'); + +var_dump(clone($c, [ 'foo' => 'foo updated in clone-with' ])); + +?> +--EXPECTF-- +object(Clazz)#%d (2) { + ["foo"]=> + string(25) "foo updated in clone-with" + ["bar"]=> + string(22) "bar updated in __clone" +} diff --git a/Zend/tests/clone/clone_with_008.phpt b/Zend/tests/clone/clone_with_008.phpt new file mode 100644 index 0000000000000..e72f21d1e0ec5 --- /dev/null +++ b/Zend/tests/clone/clone_with_008.phpt @@ -0,0 +1,35 @@ +--TEST-- +Clone with readonly +--FILE-- +b = '__clone'; + } +} + +$c = new Clazz('default', 'default'); + +var_dump(clone($c, [ 'a' => "with" ])); + +try { + var_dump(clone($c, [ 'b' => "with" ])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +object(Clazz)#%d (2) { + ["a"]=> + string(4) "with" + ["b"]=> + string(7) "__clone" +} +Error: Cannot modify readonly property Clazz::$b diff --git a/Zend/tests/clone/clone_with_009.phpt b/Zend/tests/clone/clone_with_009.phpt new file mode 100644 index 0000000000000..c6a7d2d18b982 --- /dev/null +++ b/Zend/tests/clone/clone_with_009.phpt @@ -0,0 +1,72 @@ +--TEST-- +Clone with lazy objects +--FILE-- + 2 ]); + + var_dump($reflector->isUninitializedLazyObject($obj)); + var_dump($obj); + var_dump($reflector->isUninitializedLazyObject($clone)); + var_dump($clone); +} + +$reflector = new ReflectionClass(C::class); + +$obj = $reflector->newLazyGhost(function ($obj) { + var_dump("initializer"); + $obj->__construct(); +}); + +test('Ghost', $obj); + +$obj = $reflector->newLazyProxy(function ($obj) { + var_dump("initializer"); + return new C(); +}); + +test('Proxy', $obj); + +?> +--EXPECTF-- +# Ghost: +string(11) "initializer" +bool(false) +object(C)#%d (1) { + ["a"]=> + int(1) +} +bool(false) +object(C)#%d (1) { + ["a"]=> + int(2) +} +# Proxy: +string(11) "initializer" +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(1) + } +} +bool(false) +lazy proxy object(C)#%d (1) { + ["instance"]=> + object(C)#%d (1) { + ["a"]=> + int(2) + } +} diff --git a/Zend/tests/clone/clone_with_010.phpt b/Zend/tests/clone/clone_with_010.phpt new file mode 100644 index 0000000000000..97afa737ab091 --- /dev/null +++ b/Zend/tests/clone/clone_with_010.phpt @@ -0,0 +1,21 @@ +--TEST-- +Clone with native classes +--FILE-- + "something" ])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +try { + var_dump(clone(new \Random\Engine\Xoshiro256StarStar(), [ 'with' => "something" ])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Error: Trying to clone an uncloneable object of class Random\Engine\Secure +Error: Trying to clone an object with updated properties that is not compatible Random\Engine\Xoshiro256StarStar diff --git a/Zend/tests/clone/clone_with_011.phpt b/Zend/tests/clone/clone_with_011.phpt new file mode 100644 index 0000000000000..5f8e99bb65f2f --- /dev/null +++ b/Zend/tests/clone/clone_with_011.phpt @@ -0,0 +1,18 @@ +--TEST-- +Clone with name mangling +--FILE-- + 'updated'])); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Error: Cannot access property starting with "\0" diff --git a/Zend/tests/generators/clone.phpt b/Zend/tests/generators/clone.phpt index 1e8da25136a13..748b826365cc0 100644 --- a/Zend/tests/generators/clone.phpt +++ b/Zend/tests/generators/clone.phpt @@ -7,12 +7,14 @@ function gen() { yield; } -$gen = gen(); -clone $gen; + +try { + $gen = gen(); + clone $gen; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class Generator in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class Generator diff --git a/Zend/tests/magic_methods/bug73288.phpt b/Zend/tests/magic_methods/bug73288.phpt index 52e8eedeaf013..09819f0853cca 100644 --- a/Zend/tests/magic_methods/bug73288.phpt +++ b/Zend/tests/magic_methods/bug73288.phpt @@ -28,7 +28,8 @@ test_clone(); --EXPECTF-- Fatal error: Uncaught Exception: No Cloneable in %sbug73288.php:%d Stack trace: -#0 %s(%d): NoClone->__clone() -#1 %s(%d): test_clone() -#2 {main} +#0 [internal function]: NoClone->__clone() +#1 %s(%d): clone(Object(NoClone)) +#2 %s(%d): test_clone() +#3 {main} thrown in %sbug73288.php on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e0006e7d7275f..259481ebfb0e8 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3641,6 +3641,7 @@ static void zend_disable_function(const char *function_name, size_t function_nam if (UNEXPECTED( (function_name_length == strlen("exit") && !memcmp(function_name, "exit", strlen("exit"))) || (function_name_length == strlen("die") && !memcmp(function_name, "die", strlen("die"))) + || (function_name_length == strlen("clone") && !memcmp(function_name, "clone", strlen("clone"))) )) { zend_error(E_WARNING, "Cannot disable function %s()", function_name); return; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 6a2826160e9c0..736a46dac5b05 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2346,7 +2346,15 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appendc(str, '`'); break; case ZEND_AST_CLONE: - PREFIX_OP("clone ", 270, 271); + smart_str_appends(str, "clone"); + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appends(str, ", "); + zend_ast_export_ex(str, ast->child[1], 0, indent); + } + smart_str_appendc(str, ')'); + break; case ZEND_AST_PRINT: PREFIX_OP("print ", 60, 61); case ZEND_AST_INCLUDE_OR_EVAL: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..261cccc571fde 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -89,7 +89,6 @@ enum _zend_ast_kind { ZEND_AST_ISSET, ZEND_AST_SILENCE, ZEND_AST_SHELL_EXEC, - ZEND_AST_CLONE, ZEND_AST_PRINT, ZEND_AST_INCLUDE_OR_EVAL, ZEND_AST_UNARY_OP, @@ -154,6 +153,7 @@ enum _zend_ast_kind { ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, + ZEND_AST_CLONE, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 7a07ceadce2e2..75508ce89bd50 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -69,6 +69,63 @@ zend_result zend_startup_builtin_functions(void) /* {{{ */ } /* }}} */ +ZEND_FUNCTION(clone) +{ + zend_object *zobj; + HashTable *with = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJ(zobj) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT(with) + ZEND_PARSE_PARAMETERS_END(); + + zend_class_entry *scope = zend_get_executed_scope(); + + zend_class_entry *ce = zobj->ce; + zend_function *clone = ce->clone; + + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { + zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + + if (clone && !(clone->common.fn_flags & ZEND_ACC_PUBLIC)) { + if (clone->common.scope != scope) { + if (UNEXPECTED(clone->common.fn_flags & ZEND_ACC_PRIVATE) + || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { + zend_throw_error(NULL, "Call to %s %s::__clone() from %s%s", + zend_visibility_string(clone->common.fn_flags), ZSTR_VAL(clone->common.scope->name), + scope ? "scope " : "global scope", + scope ? ZSTR_VAL(scope->name) : "" + ); + RETURN_THROWS(); + } + } + } + + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + cloned = zobj->handlers->clone_obj_with(zobj, scope, with); + } else { + if (UNEXPECTED(with != NULL && zend_hash_num_elements(with) > 0)) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + cloned = zobj->handlers->clone_obj(zobj); + } + + if (EG(exception)) { + if (cloned) { + OBJ_RELEASE(cloned); + } + + RETURN_THROWS(); + } + + RETURN_OBJ(cloned); +} + ZEND_FUNCTION(exit) { zend_string *str = NULL; diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index 7f316835aea6b..ec1c31d994e2d 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -7,6 +7,8 @@ class stdClass { } +function _clone(object $object, array $withProperties = []): object {} + function exit(string|int $status = 0): never {} /** @alias exit */ diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index 9498b8292f892..9d85ced893640 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,10 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a24761186f1ddf758e648b0a764826537cbd33b9 */ + * Stub hash: 392169c2b4b974791e57c474be4ea2f8c9b58aae */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_clone, 0, 1, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, withProperties, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0) ZEND_ARG_TYPE_MASK(0, status, MAY_BE_STRING|MAY_BE_LONG, "0") @@ -243,6 +248,7 @@ static const zend_frameless_function_info frameless_function_infos_class_exists[ { 0 }, }; +ZEND_FUNCTION(clone); ZEND_FUNCTION(exit); ZEND_FUNCTION(zend_version); ZEND_FUNCTION(func_num_args); @@ -306,6 +312,7 @@ ZEND_FUNCTION(gc_disable); ZEND_FUNCTION(gc_status); static const zend_function_entry ext_functions[] = { + ZEND_FE(clone, arginfo_clone) ZEND_FE(exit, arginfo_exit) ZEND_RAW_FENTRY("die", zif_exit, arginfo_die, 0, NULL, NULL) ZEND_FE(zend_version, arginfo_zend_version) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0669d106f15e9..4e45a85a344ff 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5404,7 +5404,14 @@ static void zend_compile_clone(znode *result, zend_ast *ast) /* {{{ */ znode obj_node; zend_compile_expr(&obj_node, obj_ast); - zend_emit_op_tmp(result, ZEND_CLONE, &obj_node, NULL); + znode value_node; + if (ast->child[1]) { + zend_compile_expr(&value_node, ast->child[1]); + } else { + value_node.op_type = IS_UNUSED; + } + + zend_emit_op_tmp(result, ZEND_CLONE, &obj_node, &value_node); } /* }}} */ diff --git a/Zend/zend_iterators.c b/Zend/zend_iterators.c index f67033b11161c..64dbb0541a80d 100644 --- a/Zend/zend_iterators.c +++ b/Zend/zend_iterators.c @@ -31,6 +31,7 @@ static const zend_object_handlers iterator_object_handlers = { iter_wrapper_free, iter_wrapper_dtor, NULL, /* clone_obj */ + NULL, /* clone_obj_with */ NULL, /* prop read */ NULL, /* prop write */ NULL, /* read dim */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 08b2ac6b3f39b..51c0ca3bdaa07 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -285,7 +285,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body -%type optional_parameter_list +%type optional_parameter_list parens_less_argument_list non_empty_parens_less_argument_list %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 @@ -905,6 +905,22 @@ argument_list: | '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); } ; +parens_less_argument_list: + '(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); } + | '(' non_empty_parens_less_argument_list possible_comma ')' { $$ = $2; } + | '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); } +; + +non_empty_parens_less_argument_list: + expr ',' argument + { $$ = zend_ast_list_add(zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1), $3); } + | identifier ':' expr + { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3)); } + | T_ELLIPSIS expr { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create(ZEND_AST_UNPACK, $2)); } + | non_empty_parens_less_argument_list ',' argument + { $$ = zend_ast_list_add($1, $3); } +; + non_empty_argument_list: argument { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); } @@ -1226,7 +1242,16 @@ expr: { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); } | variable '=' ampersand variable { $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); } - | T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2); } + | T_CLONE parens_less_argument_list { + zend_ast *name = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CLONE)); + name->attr = ZEND_NAME_FQ; + $$ = zend_ast_create(ZEND_AST_CALL, name, $2); + } + | T_CLONE expr { + zend_ast *name = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CLONE)); + name->attr = ZEND_NAME_FQ; + $$ = zend_ast_create(ZEND_AST_CALL, name, zend_ast_create_list(1, ZEND_AST_ARG_LIST, $2)); + } | variable T_PLUS_EQUAL expr { $$ = zend_ast_create_assign_op(ZEND_ADD, $1, $3); } | variable T_MINUS_EQUAL expr diff --git a/Zend/zend_lazy_objects.c b/Zend/zend_lazy_objects.c index d1b950160e1cc..0300f078e48a5 100644 --- a/Zend/zend_lazy_objects.c +++ b/Zend/zend_lazy_objects.c @@ -709,7 +709,7 @@ ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object) /* Initialize object and clone it. For proxies, we clone both the proxy and its * real instance, and we don't call __clone() on the proxy. */ -zend_object *zend_lazy_object_clone(zend_object *old_obj) +zend_object *zend_lazy_object_clone(zend_object *old_obj, zend_class_entry *scope, const HashTable *properties) { ZEND_ASSERT(zend_object_is_lazy(old_obj)); @@ -724,7 +724,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj) } if (!zend_object_is_lazy_proxy(old_obj)) { - return zend_objects_clone_obj(old_obj); + return zend_objects_clone_obj_with(old_obj, scope, properties); } zend_lazy_object_info *info = zend_lazy_object_get_info(old_obj); @@ -748,7 +748,7 @@ zend_object *zend_lazy_object_clone(zend_object *old_obj) zend_lazy_object_info *new_info = emalloc(sizeof(*info)); *new_info = *info; - new_info->u.instance = zend_objects_clone_obj(info->u.instance); + new_info->u.instance = zend_objects_clone_obj_with(info->u.instance, scope, properties); zend_lazy_object_set_info(new_proxy, new_info); diff --git a/Zend/zend_lazy_objects.h b/Zend/zend_lazy_objects.h index 64f68d66360cd..f1850f0b97c34 100644 --- a/Zend/zend_lazy_objects.h +++ b/Zend/zend_lazy_objects.h @@ -71,7 +71,7 @@ zend_object *zend_lazy_object_get_instance(zend_object *obj); zend_lazy_object_flags_t zend_lazy_object_get_flags(zend_object *obj); void zend_lazy_object_del_info(zend_object *obj); ZEND_API HashTable *zend_lazy_object_get_properties(zend_object *object); -zend_object *zend_lazy_object_clone(zend_object *old_obj); +zend_object *zend_lazy_object_clone(zend_object *old_obj, zend_class_entry *scope, const HashTable *properties); HashTable *zend_lazy_object_debug_info(zend_object *object, int *is_temp); HashTable *zend_lazy_object_get_gc(zend_object *zobj, zval **table, int *n); bool zend_lazy_object_decr_lazy_props(zend_object *obj); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index ba26ac7128a3d..bef087276bce4 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2536,6 +2536,7 @@ ZEND_API const zend_object_handlers std_object_handlers = { zend_object_std_dtor, /* free_obj */ zend_objects_destroy_object, /* dtor_obj */ zend_objects_clone_obj, /* clone_obj */ + zend_objects_clone_obj_with, /* clone_obj_with */ zend_std_read_property, /* read_property */ zend_std_write_property, /* write_property */ diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 7e7d3df37a6ad..7b99f56454f18 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -180,6 +180,7 @@ typedef void (*zend_object_free_obj_t)(zend_object *object); typedef void (*zend_object_dtor_obj_t)(zend_object *object); typedef zend_object* (*zend_object_clone_obj_t)(zend_object *object); +typedef zend_object* (*zend_object_clone_obj_with_t)(zend_object *object, zend_class_entry *scope, const HashTable *properties); /* Get class name for display in var_dump and other debugging functions. * Must be defined and must return a non-NULL value. */ @@ -209,6 +210,7 @@ struct _zend_object_handlers { zend_object_free_obj_t free_obj; /* required */ zend_object_dtor_obj_t dtor_obj; /* required */ zend_object_clone_obj_t clone_obj; /* optional */ + zend_object_clone_obj_with_t clone_obj_with; /* optional */ zend_object_read_property_t read_property; /* required */ zend_object_write_property_t write_property; /* required */ zend_object_read_dimension_t read_dimension; /* required */ diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index fd0e97c5f4131..4dfa79416d1d0 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -213,9 +213,9 @@ ZEND_API zend_object* ZEND_FASTCALL zend_objects_new(zend_class_entry *ce) return object; } -ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object) +ZEND_API void ZEND_FASTCALL zend_objects_clone_members_ex(zend_object *new_object, zend_object *old_object, zend_class_entry *scope, const HashTable *properties) { - bool has_clone_method = old_object->ce->clone != NULL; + bool has_clone_method = old_object->ce->clone != NULL || properties != NULL; if (old_object->ce->default_properties_count) { zval *src = old_object->properties_table; @@ -289,7 +289,35 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, if (has_clone_method) { GC_ADDREF(new_object); - zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL); + if (old_object->ce->clone) { + zend_call_known_instance_method_with_0_params(new_object->ce->clone, new_object, NULL); + } + + if (EXPECTED(!EG(exception)) && properties != NULL) { + zend_class_entry *old_scope = EG(fake_scope); + + EG(fake_scope) = scope; + + zend_ulong num_key; + zend_string *key; + zval *val; + ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, val) { + if (UNEXPECTED(key == NULL)) { + key = zend_long_to_str(num_key); + new_object->handlers->write_property(new_object, key, val, NULL); + zend_string_release_ex(key, false); + } else { + new_object->handlers->write_property(new_object, key, val, NULL); + } + + if (UNEXPECTED(EG(exception))) { + break; + } + } ZEND_HASH_FOREACH_END(); + + EG(fake_scope) = old_scope; + } + if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { @@ -303,12 +331,33 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, } } -ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object) +ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zend_object *old_object) +{ + ZEND_ASSERT(old_object->ce == new_object->ce); + + zend_objects_clone_members_ex(new_object, old_object, old_object->ce, NULL); +} + +ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, zend_class_entry *scope, const HashTable *properties) { zend_object *new_object; + /* Compatibility with code that only overrides clone_obj. */ + if (UNEXPECTED(old_object->handlers->clone_obj != zend_objects_clone_obj)) { + if (!old_object->handlers->clone_obj) { + zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(old_object->ce->name)); + return NULL; + } + if (properties && zend_hash_num_elements(properties) > 0) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(old_object->ce->name)); + return NULL; + } else { + return old_object->handlers->clone_obj(old_object); + } + } + if (UNEXPECTED(zend_object_is_lazy(old_object))) { - return zend_lazy_object_clone(old_object); + return zend_lazy_object_clone(old_object, scope, properties); } /* assume that create isn't overwritten, so when clone depends on the @@ -325,7 +374,12 @@ ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object) } while (p != end); } - zend_objects_clone_members(new_object, old_object); + zend_objects_clone_members_ex(new_object, old_object, scope, properties); return new_object; } + +ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object) +{ + return zend_objects_clone_obj_with(old_object, old_object->ce, NULL); +} diff --git a/Zend/zend_objects.h b/Zend/zend_objects.h index 41e3bcd9594b1..9aea4a6245b36 100644 --- a/Zend/zend_objects.h +++ b/Zend/zend_objects.h @@ -30,6 +30,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, ZEND_API void zend_object_std_dtor(zend_object *object); ZEND_API void zend_objects_destroy_object(zend_object *object); ZEND_API zend_object *zend_objects_clone_obj(zend_object *object); +ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *object, zend_class_entry *scope, const HashTable *properties); void zend_object_dtor_dynamic_properties(zend_object *object); void zend_object_dtor_property(zend_object *object, zval *p); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index e9e2b947a6c91..cad79742506df 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -575,6 +575,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_UNKNOWN, "unknown") \ _(ZEND_STR_UNKNOWN_CAPITALIZED, "Unknown") \ _(ZEND_STR_EXIT, "exit") \ + _(ZEND_STR_CLONE, "clone") \ _(ZEND_STR_EVAL, "eval") \ _(ZEND_STR_INCLUDE, "include") \ _(ZEND_STR_REQUIRE, "require") \ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 617e114dd05db..91954d9620de3 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6001,7 +6001,6 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY) zend_object *zobj; zend_class_entry *ce, *scope; zend_function *clone; - zend_object_clone_obj_t clone_call; SAVE_OPLINE(); obj = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R); @@ -6024,6 +6023,7 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY) } zend_throw_error(NULL, "__clone method called on non-object"); FREE_OP1(); + FREE_OP2(); HANDLE_EXCEPTION(); } } while (0); @@ -6031,10 +6031,10 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY) zobj = Z_OBJ_P(obj); ce = zobj->ce; clone = ce->clone; - clone_call = zobj->handlers->clone_obj; - if (UNEXPECTED(clone_call == NULL)) { + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); FREE_OP1(); + FREE_OP2(); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } @@ -6046,15 +6046,55 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY) || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { zend_wrong_clone_call(clone, scope); FREE_OP1(); + FREE_OP2(); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } } - ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj)); + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + scope = EX(func)->op_array.scope; + if (OP2_TYPE != IS_UNUSED) { + zval *properties = GET_OP2_ZVAL_PTR(BP_VAR_R); + if (Z_TYPE_P(properties) != IS_ARRAY) { + zend_type_error("Updated properties must be of type array, %s given", zend_zval_value_name(properties)); + FREE_OP1(); + FREE_OP2(); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj_with(zobj, scope, Z_ARR_P(properties)); + } else { + cloned = zobj->handlers->clone_obj_with(zobj, scope, NULL); + } + + if (UNEXPECTED(EG(exception))) { + if (cloned) { + OBJ_RELEASE(cloned); + } + FREE_OP1(); + FREE_OP2(); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } else { + if (OP2_TYPE != IS_UNUSED) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + FREE_OP1(); + FREE_OP2(); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj(zobj); + } + ZVAL_OBJ(EX_VAR(opline->result.var), cloned); FREE_OP1(); + FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 791e4b4e88437..3a0f2bcd729d6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5175,7 +5175,6 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CONST_ zend_object *zobj; zend_class_entry *ce, *scope; zend_function *clone; - zend_object_clone_obj_t clone_call; SAVE_OPLINE(); obj = RT_CONSTANT(opline, opline->op1); @@ -5198,6 +5197,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CONST_ } zend_throw_error(NULL, "__clone method called on non-object"); + FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); } } while (0); @@ -5205,10 +5205,10 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CONST_ zobj = Z_OBJ_P(obj); ce = zobj->ce; clone = ce->clone; - clone_call = zobj->handlers->clone_obj; - if (UNEXPECTED(clone_call == NULL)) { + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } @@ -5220,14 +5220,55 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CONST_ || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { zend_wrong_clone_call(clone, scope); + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } + } + + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + scope = EX(func)->op_array.scope; + if (opline->op2_type != IS_UNUSED) { + zval *properties = get_zval_ptr(opline->op2_type, opline->op2, BP_VAR_R); + if (Z_TYPE_P(properties) != IS_ARRAY) { + zend_type_error("Updated properties must be of type array, %s given", zend_zval_value_name(properties)); + + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + cloned = zobj->handlers->clone_obj_with(zobj, scope, Z_ARR_P(properties)); + } else { + cloned = zobj->handlers->clone_obj_with(zobj, scope, NULL); + } + + if (UNEXPECTED(EG(exception))) { + if (cloned) { + OBJ_RELEASE(cloned); + } + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } else { + if (opline->op2_type != IS_UNUSED) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); } + + cloned = zobj->handlers->clone_obj(zobj); } - ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj)); + ZVAL_OBJ(EX_VAR(opline->result.var), cloned); + FREE_OP(opline->op2_type, opline->op2.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -15423,7 +15464,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_TMPVAR_HANDLER(ZEND zend_object *zobj; zend_class_entry *ce, *scope; zend_function *clone; - zend_object_clone_obj_t clone_call; SAVE_OPLINE(); obj = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -15446,6 +15486,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_TMPVAR_HANDLER(ZEND } zend_throw_error(NULL, "__clone method called on non-object"); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); } } while (0); @@ -15453,10 +15494,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_TMPVAR_HANDLER(ZEND zobj = Z_OBJ_P(obj); ce = zobj->ce; clone = ce->clone; - clone_call = zobj->handlers->clone_obj; - if (UNEXPECTED(clone_call == NULL)) { + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } @@ -15468,15 +15509,55 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_TMPVAR_HANDLER(ZEND || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { zend_wrong_clone_call(clone, scope); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } } - ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj)); + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + scope = EX(func)->op_array.scope; + if (opline->op2_type != IS_UNUSED) { + zval *properties = get_zval_ptr(opline->op2_type, opline->op2, BP_VAR_R); + if (Z_TYPE_P(properties) != IS_ARRAY) { + zend_type_error("Updated properties must be of type array, %s given", zend_zval_value_name(properties)); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj_with(zobj, scope, Z_ARR_P(properties)); + } else { + cloned = zobj->handlers->clone_obj_with(zobj, scope, NULL); + } + + if (UNEXPECTED(EG(exception))) { + if (cloned) { + OBJ_RELEASE(cloned); + } + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } else { + if (opline->op2_type != IS_UNUSED) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj(zobj); + } + ZVAL_OBJ(EX_VAR(opline->result.var), cloned); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); + FREE_OP(opline->op2_type, opline->op2.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -33518,7 +33599,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_UNUSED_HANDLER(ZEND zend_object *zobj; zend_class_entry *ce, *scope; zend_function *clone; - zend_object_clone_obj_t clone_call; SAVE_OPLINE(); obj = &EX(This); @@ -33541,6 +33621,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_UNUSED_HANDLER(ZEND } zend_throw_error(NULL, "__clone method called on non-object"); + FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); } } while (0); @@ -33548,10 +33629,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_UNUSED_HANDLER(ZEND zobj = Z_OBJ_P(obj); ce = zobj->ce; clone = ce->clone; - clone_call = zobj->handlers->clone_obj; - if (UNEXPECTED(clone_call == NULL)) { + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } @@ -33563,14 +33644,55 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_UNUSED_HANDLER(ZEND || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { zend_wrong_clone_call(clone, scope); + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } + } + + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + scope = EX(func)->op_array.scope; + if (opline->op2_type != IS_UNUSED) { + zval *properties = get_zval_ptr(opline->op2_type, opline->op2, BP_VAR_R); + if (Z_TYPE_P(properties) != IS_ARRAY) { + zend_type_error("Updated properties must be of type array, %s given", zend_zval_value_name(properties)); + + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } + + cloned = zobj->handlers->clone_obj_with(zobj, scope, Z_ARR_P(properties)); + } else { + cloned = zobj->handlers->clone_obj_with(zobj, scope, NULL); + } + + if (UNEXPECTED(EG(exception))) { + if (cloned) { + OBJ_RELEASE(cloned); + } + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } else { + if (opline->op2_type != IS_UNUSED) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); } + + cloned = zobj->handlers->clone_obj(zobj); } - ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj)); + ZVAL_OBJ(EX_VAR(opline->result.var), cloned); + FREE_OP(opline->op2_type, opline->op2.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -41037,7 +41159,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CV_HANDLER(ZEND_OPC zend_object *zobj; zend_class_entry *ce, *scope; zend_function *clone; - zend_object_clone_obj_t clone_call; SAVE_OPLINE(); obj = EX_VAR(opline->op1.var); @@ -41060,6 +41181,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CV_HANDLER(ZEND_OPC } zend_throw_error(NULL, "__clone method called on non-object"); + FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); } } while (0); @@ -41067,10 +41189,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CV_HANDLER(ZEND_OPC zobj = Z_OBJ_P(obj); ce = zobj->ce; clone = ce->clone; - clone_call = zobj->handlers->clone_obj; - if (UNEXPECTED(clone_call == NULL)) { + if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) { zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name)); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } @@ -41082,14 +41204,55 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CV_HANDLER(ZEND_OPC || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) { zend_wrong_clone_call(clone, scope); + FREE_OP(opline->op2_type, opline->op2.var); ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } } - ZVAL_OBJ(EX_VAR(opline->result.var), clone_call(zobj)); + zend_object *cloned; + if (zobj->handlers->clone_obj_with) { + scope = EX(func)->op_array.scope; + if (opline->op2_type != IS_UNUSED) { + zval *properties = get_zval_ptr(opline->op2_type, opline->op2, BP_VAR_R); + if (Z_TYPE_P(properties) != IS_ARRAY) { + zend_type_error("Updated properties must be of type array, %s given", zend_zval_value_name(properties)); + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj_with(zobj, scope, Z_ARR_P(properties)); + } else { + cloned = zobj->handlers->clone_obj_with(zobj, scope, NULL); + } + + if (UNEXPECTED(EG(exception))) { + if (cloned) { + OBJ_RELEASE(cloned); + } + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + } else { + if (opline->op2_type != IS_UNUSED) { + zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name)); + + FREE_OP(opline->op2_type, opline->op2.var); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + HANDLE_EXCEPTION(); + } + + cloned = zobj->handlers->clone_obj(zobj); + } + + ZVAL_OBJ(EX_VAR(opline->result.var), cloned); + + FREE_OP(opline->op2_type, opline->op2.var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } diff --git a/build/gen_stub.php b/build/gen_stub.php index 5e06a53172e07..fbf09fd7f7b5a 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1009,6 +1009,9 @@ class FunctionName implements FunctionOrMethodName { private /* readonly */ Name $name; public function __construct(Name $name) { + if ($name->name === '_clone') { + $name = new Name('clone', $name->getAttributes()); + } $this->name = $name; } @@ -3054,6 +3057,7 @@ class PropertyInfo extends VariableLike private const PHP_85_KNOWN = [ "self" => "ZEND_STR_SELF", "parent" => "ZEND_STR_PARENT", + "clone" => "ZEND_STR_CLONE", ]; /** diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index af980b7b86f2a..638fc5d8a3ae6 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -514,6 +514,7 @@ zend_object_handlers php_com_object_handlers = { php_com_object_free_storage, zend_objects_destroy_object, php_com_object_clone, + NULL, /* clone_with */ com_property_read, com_property_write, com_read_dimension, diff --git a/ext/com_dotnet/com_saproxy.c b/ext/com_dotnet/com_saproxy.c index ea0e9e47a13d9..ec79faa30a32b 100644 --- a/ext/com_dotnet/com_saproxy.c +++ b/ext/com_dotnet/com_saproxy.c @@ -402,6 +402,7 @@ zend_object_handlers php_com_saproxy_handlers = { saproxy_free_storage, zend_objects_destroy_object, saproxy_clone, + NULL, /* clone_with */ saproxy_property_read, saproxy_property_write, saproxy_read_dimension, diff --git a/ext/dom/tests/modern/token_list/clone.phpt b/ext/dom/tests/modern/token_list/clone.phpt index 039551f2d43d8..e0c71e9fd7910 100644 --- a/ext/dom/tests/modern/token_list/clone.phpt +++ b/ext/dom/tests/modern/token_list/clone.phpt @@ -7,11 +7,12 @@ dom $dom = DOM\XMLDocument::createFromString(''); $element = $dom->documentElement; -clone $element->classList; +try { + clone $element->classList; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class Dom\TokenList in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class Dom\TokenList diff --git a/ext/gd/tests/gdimage_prevent_cloning.phpt b/ext/gd/tests/gdimage_prevent_cloning.phpt index 426f7d9c48f6c..609e6f99bbfa9 100644 --- a/ext/gd/tests/gdimage_prevent_cloning.phpt +++ b/ext/gd/tests/gdimage_prevent_cloning.phpt @@ -5,12 +5,13 @@ gd --FILE-- getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class GdImage in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class GdImage diff --git a/ext/mysqli/tests/mysqli_driver_unclonable.phpt b/ext/mysqli/tests/mysqli_driver_unclonable.phpt index 54b97ef36c931..7041a024f67cb 100644 --- a/ext/mysqli/tests/mysqli_driver_unclonable.phpt +++ b/ext/mysqli/tests/mysqli_driver_unclonable.phpt @@ -4,12 +4,14 @@ Trying to clone mysqli_driver object mysqli --FILE-- getMessage(), PHP_EOL; +} + ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class mysqli_driver in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class mysqli_driver diff --git a/ext/mysqli/tests/mysqli_result_unclonable.phpt b/ext/mysqli/tests/mysqli_result_unclonable.phpt index b54c39f7a170f..98770406bb713 100644 --- a/ext/mysqli/tests/mysqli_result_unclonable.phpt +++ b/ext/mysqli/tests/mysqli_result_unclonable.phpt @@ -17,11 +17,11 @@ require_once 'skipifconnectfailure.inc'; if (!($res = mysqli_query($link, "SELECT 'good' AS morning"))) printf("[002] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - $res_clone = clone $res; - print "done!"; + try { + $res_clone = clone $res; + } catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; + } ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class mysqli_result in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class mysqli_result diff --git a/ext/mysqli/tests/mysqli_stmt_unclonable.phpt b/ext/mysqli/tests/mysqli_stmt_unclonable.phpt index 29658bb25c669..c1e01d37e36c0 100644 --- a/ext/mysqli/tests/mysqli_stmt_unclonable.phpt +++ b/ext/mysqli/tests/mysqli_stmt_unclonable.phpt @@ -17,12 +17,11 @@ require_once 'skipifconnectfailure.inc'; if (!$stmt = mysqli_stmt_init($link)) printf("[002] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - /* no, still bails out */ - $stmt_clone = clone $stmt; - print "done!"; + try { + $stmt_clone = clone $stmt; + } catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; + } ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class mysqli_stmt in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class mysqli_stmt diff --git a/ext/mysqli/tests/mysqli_unclonable.phpt b/ext/mysqli/tests/mysqli_unclonable.phpt index 0772854ed96a1..6e42171606c24 100644 --- a/ext/mysqli/tests/mysqli_unclonable.phpt +++ b/ext/mysqli/tests/mysqli_unclonable.phpt @@ -14,13 +14,12 @@ require_once 'skipifconnectfailure.inc'; printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", $host, $user, $db, $port, $socket); - $link_clone = clone $link; - mysqli_close($link); + try { + $link_clone = clone $link; + } catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; + } - print "done!"; ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class mysqli in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class mysqli diff --git a/ext/pdo/tests/bug_77849.phpt b/ext/pdo/tests/bug_77849.phpt index 6fbf56e869b70..bbb0f3e8595e2 100644 --- a/ext/pdo/tests/bug_77849.phpt +++ b/ext/pdo/tests/bug_77849.phpt @@ -15,10 +15,11 @@ if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.dirname(__FILE_ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); -$db2 = clone $db; +try { + $db2 = clone $db; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class PDO in %s -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class PDO diff --git a/ext/pdo/tests/bug_77849_2.phpt b/ext/pdo/tests/bug_77849_2.phpt index 6453a79312e2a..3e481eedb1e1a 100644 --- a/ext/pdo/tests/bug_77849_2.phpt +++ b/ext/pdo/tests/bug_77849_2.phpt @@ -4,13 +4,14 @@ PDO Common: Bug #77849 (inconsistent state of cloned statament object) pdo --FILE-- ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class PDOStatement in %s:4 -Stack trace: -#0 {main} - thrown in %s on line 4 +try { + $stmt = new PDOStatement(); + clone $stmt; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} +?> +--EXPECT-- +Error: Trying to clone an uncloneable object of class PDOStatement diff --git a/ext/reflection/tests/ReflectionClass_CannotClone_basic.phpt b/ext/reflection/tests/ReflectionClass_CannotClone_basic.phpt index 58ce9c65dce0f..64d884d6ae02c 100644 --- a/ext/reflection/tests/ReflectionClass_CannotClone_basic.phpt +++ b/ext/reflection/tests/ReflectionClass_CannotClone_basic.phpt @@ -6,10 +6,11 @@ TestFest PHP|Tek --FILE-- getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class ReflectionClass in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class ReflectionClass diff --git a/ext/reflection/tests/ReflectionClass_isCloneable_001.phpt b/ext/reflection/tests/ReflectionClass_isCloneable_001.phpt index 0b3701f1bb610..bde3a60a1e7f9 100644 --- a/ext/reflection/tests/ReflectionClass_isCloneable_001.phpt +++ b/ext/reflection/tests/ReflectionClass_isCloneable_001.phpt @@ -49,10 +49,14 @@ $obj = new ReflectionClass('xmlwriter'); var_dump($obj->isCloneable()); $obj = new ReflectionObject(new XMLWriter); var_dump($obj->isCloneable()); -$h = clone new xmlwriter; +try { + $h = clone new xmlwriter; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- +--EXPECT-- User class bool(true) bool(true) @@ -68,8 +72,4 @@ bool(true) Internal class - XMLWriter bool(false) bool(false) - -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class XMLWriter in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +Error: Trying to clone an uncloneable object of class XMLWriter diff --git a/ext/xml/tests/bug78563.phpt b/ext/xml/tests/bug78563.phpt index 4e1bb5d63bf57..dc7d5fe02dc26 100644 --- a/ext/xml/tests/bug78563.phpt +++ b/ext/xml/tests/bug78563.phpt @@ -5,13 +5,13 @@ xml --FILE-- getMessage(), PHP_EOL; +} ?> -===DONE=== ---EXPECTF-- -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class XMLParser in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Trying to clone an uncloneable object of class XMLParser diff --git a/ext/xmlreader/tests/bug51936.phpt b/ext/xmlreader/tests/bug51936.phpt index 00a8134d1a45f..6014c5550a629 100644 --- a/ext/xmlreader/tests/bug51936.phpt +++ b/ext/xmlreader/tests/bug51936.phpt @@ -4,20 +4,19 @@ Bug #51936 (Crash with clone XMLReader) xmlreader --FILE-- xml(""); $xmlreader->next(); -$xmlreader2 = clone $xmlreader; -$xmlreader2->next(); -?> -Done ---EXPECTF-- -Test -Fatal error: Uncaught Error: Trying to clone an uncloneable object of class XMLReader in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +try { + $xmlreader2 = clone $xmlreader; + $xmlreader2->next(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Error: Trying to clone an uncloneable object of class XMLReader diff --git a/tests/classes/factory_and_singleton_007.phpt b/tests/classes/factory_and_singleton_007.phpt index 2c35090eed5e0..fc232bdb8655a 100644 --- a/tests/classes/factory_and_singleton_007.phpt +++ b/tests/classes/factory_and_singleton_007.phpt @@ -8,14 +8,13 @@ class test { } } -$obj = new test; -$clone = clone $obj; -$obj = NULL; +try { + $obj = new test; + $clone = clone $obj; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} -echo "Done\n"; ?> ---EXPECTF-- -Fatal error: Uncaught Error: Call to protected test::__clone() from global scope in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Call to protected test::__clone() from global scope diff --git a/tests/classes/factory_and_singleton_008.phpt b/tests/classes/factory_and_singleton_008.phpt index 2b2c0721c75e5..672c083270730 100644 --- a/tests/classes/factory_and_singleton_008.phpt +++ b/tests/classes/factory_and_singleton_008.phpt @@ -8,14 +8,13 @@ class test { } } -$obj = new test; -$clone = clone $obj; -$obj = NULL; +try { + $obj = new test; + $clone = clone $obj; +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} -echo "Done\n"; ?> ---EXPECTF-- -Fatal error: Uncaught Error: Call to private test::__clone() from global scope in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d +--EXPECT-- +Error: Call to private test::__clone() from global scope