Skip to content

Commit ca49a7b

Browse files
TimWollaedorian
andauthored
RFC: Turn clone() into a function (#18919)
RFC: https://wiki.php.net/rfc/clone_with_v2 Co-authored-by: Volker Dusch <[email protected]>
1 parent 5ed8b2b commit ca49a7b

25 files changed

+227
-58
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ PHP NEWS
5959
. Added support for `final` with constructor property promotion.
6060
(DanielEScherzer)
6161
. Do not use RTLD_DEEPBIND if dlmopen is available. (Daniil Gentili)
62+
. Make `clone() a function. (timwolla, edorian)
6263

6364
- Curl:
6465
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ PHP 8.5 UPGRADE NOTES
374374
. get_exception_handler() allows retrieving the current user-defined exception
375375
handler function.
376376
RFC: https://wiki.php.net/rfc/get-error-exception-handler
377+
. The clone language construct is now a function.
378+
RFC: https://wiki.php.net/rfc/clone_with_v2
377379

378380
- Curl:
379381
. curl_multi_get_handles() allows retrieving all CurlHandles current

Zend/Optimizer/zend_func_infos.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* This is a generated file, edit the .stub.php files instead. */
22

33
static const func_info_t func_infos[] = {
4+
F1("clone", MAY_BE_OBJECT),
45
F1("zend_version", MAY_BE_STRING),
56
FN("func_get_args", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ANY),
67
F1("get_class_vars", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF),

Zend/tests/assert/expect_015.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ assert(0 && ($a = function () {
183183
$x = $a ?? $b;
184184
[$a, $b, $c] = [1, 2 => 'x', 'z' => 'c'];
185185
@foo();
186-
$y = clone $x;
186+
$y = \clone($x);
187187
yield 1 => 2;
188188
yield from $x;
189189
}))

Zend/tests/clone/ast.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Ast Printing
3+
--FILE--
4+
<?php
5+
6+
$x = new stdClass();
7+
8+
9+
try {
10+
assert(false && $y = clone $x);
11+
} catch (Error $e) {
12+
echo $e->getMessage(), PHP_EOL;
13+
}
14+
15+
try {
16+
assert(false && $y = clone($x));
17+
} catch (Error $e) {
18+
echo $e->getMessage(), PHP_EOL;
19+
}
20+
21+
try {
22+
assert(false && $y = clone(...));
23+
} catch (Error $e) {
24+
echo $e->getMessage(), PHP_EOL;
25+
}
26+
27+
?>
28+
--EXPECT--
29+
assert(false && ($y = \clone($x)))
30+
assert(false && ($y = \clone($x)))
31+
assert(false && ($y = \clone(...)))

Zend/tests/clone/bug36071.phpt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Bug #36071 (Engine Crash related with 'clone')
44
error_reporting=4095
55
--FILE--
66
<?php
7-
$a = clone 0;
8-
$a[0]->b = 0;
7+
try {
8+
$a = clone 0;
9+
} catch (Error $e) {
10+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
11+
}
912
?>
10-
--EXPECTF--
11-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug36071.php:2
12-
Stack trace:
13-
#0 {main}
14-
thrown in %sbug36071.php on line 2
13+
--EXPECT--
14+
TypeError: clone(): Argument #1 ($object) must be of type object, int given

Zend/tests/clone/bug42817.phpt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
Bug #42817 (clone() on a non-object does not result in a fatal error)
33
--FILE--
44
<?php
5-
$a = clone(null);
6-
array_push($a->b, $c);
5+
try {
6+
$a = clone(null);
7+
} catch (Error $e) {
8+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
9+
}
710
?>
8-
--EXPECTF--
9-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug42817.php:2
10-
Stack trace:
11-
#0 {main}
12-
thrown in %sbug42817.php on line 2
11+
--EXPECT--
12+
TypeError: clone(): Argument #1 ($object) must be of type object, null given

Zend/tests/clone/bug42818.phpt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
Bug #42818 ($foo = clone(array()); leaks memory)
33
--FILE--
44
<?php
5-
$foo = clone(array());
5+
try {
6+
$foo = clone(array());
7+
} catch (Error $e) {
8+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
9+
}
610
?>
7-
--EXPECTF--
8-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug42818.php:2
9-
Stack trace:
10-
#0 {main}
11-
thrown in %sbug42818.php on line 2
11+
--EXPECT--
12+
TypeError: clone(): Argument #1 ($object) must be of type object, array given

Zend/tests/clone/clone_001.phpt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ Using clone statement on non-object
33
--FILE--
44
<?php
55

6-
$a = clone array();
6+
try {
7+
$a = clone array();
8+
} catch (Error $e) {
9+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
10+
}
711

812
?>
9-
--EXPECTF--
10-
Fatal error: Uncaught Error: __clone method called on non-object in %s:%d
11-
Stack trace:
12-
#0 {main}
13-
thrown in %s on line %d
13+
--EXPECT--
14+
TypeError: clone(): Argument #1 ($object) must be of type object, array given

Zend/tests/clone/clone_003.phpt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ Using clone statement on undefined variable
33
--FILE--
44
<?php
55

6-
$a = clone $b;
6+
try {
7+
$a = clone $b;
8+
} catch (Error $e) {
9+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
10+
}
711

812
?>
913
--EXPECTF--
1014
Warning: Undefined variable $b in %s on line %d
11-
12-
Fatal error: Uncaught Error: __clone method called on non-object in %s:%d
13-
Stack trace:
14-
#0 {main}
15-
thrown in %s on line %d
15+
TypeError: clone(): Argument #1 ($object) must be of type object, null given

Zend/tests/clone/clone_005.phpt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
Clone as a function.
3+
--FILE--
4+
<?php
5+
6+
$x = new stdClass();
7+
8+
\var_dump(\clone($x));
9+
\var_dump(\array_map('clone', [$x, $x, $x]));
10+
\var_dump(\array_map(clone(...), [$x, $x, $x]));
11+
12+
class Foo {
13+
private function __clone() {
14+
15+
}
16+
17+
public function clone_me() {
18+
// Verify visibility when going through array_map().
19+
return array_map(\clone(...), [$this]);
20+
}
21+
}
22+
23+
$f = new Foo();
24+
25+
$clone = $f->clone_me()[0];
26+
27+
var_dump($f !== $clone);
28+
29+
?>
30+
--EXPECTF--
31+
object(stdClass)#%d (0) {
32+
}
33+
array(3) {
34+
[0]=>
35+
object(stdClass)#%d (0) {
36+
}
37+
[1]=>
38+
object(stdClass)#%d (0) {
39+
}
40+
[2]=>
41+
object(stdClass)#%d (0) {
42+
}
43+
}
44+
array(3) {
45+
[0]=>
46+
object(stdClass)#%d (0) {
47+
}
48+
[1]=>
49+
object(stdClass)#%d (0) {
50+
}
51+
[2]=>
52+
object(stdClass)#%d (0) {
53+
}
54+
}
55+
bool(true)

Zend/tests/magic_methods/bug73288.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function test_clone() {
2323
$b = clone $c->x;
2424
}
2525

26+
// No catch, because we want to test Exception::__toString().
2627
test_clone();
2728
?>
2829
--EXPECTF--

Zend/zend_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3648,6 +3648,7 @@ static void zend_disable_function(const char *function_name, size_t function_nam
36483648
if (UNEXPECTED(
36493649
(function_name_length == strlen("exit") && !memcmp(function_name, "exit", strlen("exit")))
36503650
|| (function_name_length == strlen("die") && !memcmp(function_name, "die", strlen("die")))
3651+
|| (function_name_length == strlen("clone") && !memcmp(function_name, "clone", strlen("clone")))
36513652
)) {
36523653
zend_error(E_WARNING, "Cannot disable function %s()", function_name);
36533654
return;

Zend/zend_ast.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,8 +2345,6 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
23452345
}
23462346
smart_str_appendc(str, '`');
23472347
break;
2348-
case ZEND_AST_CLONE:
2349-
PREFIX_OP("clone ", 270, 271);
23502348
case ZEND_AST_PRINT:
23512349
PREFIX_OP("print ", 60, 61);
23522350
case ZEND_AST_INCLUDE_OR_EVAL:

Zend/zend_ast.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ enum _zend_ast_kind {
8989
ZEND_AST_ISSET,
9090
ZEND_AST_SILENCE,
9191
ZEND_AST_SHELL_EXEC,
92-
ZEND_AST_CLONE,
9392
ZEND_AST_PRINT,
9493
ZEND_AST_INCLUDE_OR_EVAL,
9594
ZEND_AST_UNARY_OP,

Zend/zend_builtin_functions.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,49 @@ zend_result zend_startup_builtin_functions(void) /* {{{ */
6969
}
7070
/* }}} */
7171

72+
ZEND_FUNCTION(clone)
73+
{
74+
zend_object *zobj;
75+
76+
ZEND_PARSE_PARAMETERS_START(1, 1)
77+
Z_PARAM_OBJ(zobj)
78+
ZEND_PARSE_PARAMETERS_END();
79+
80+
/* clone() also exists as the ZEND_CLONE OPcode and both implementations must be kept in sync. */
81+
82+
zend_class_entry *scope = zend_get_executed_scope();
83+
84+
zend_class_entry *ce = zobj->ce;
85+
zend_function *clone = ce->clone;
86+
87+
if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) {
88+
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name));
89+
RETURN_THROWS();
90+
}
91+
92+
if (clone && !(clone->common.fn_flags & ZEND_ACC_PUBLIC)) {
93+
if (clone->common.scope != scope) {
94+
if (UNEXPECTED(clone->common.fn_flags & ZEND_ACC_PRIVATE)
95+
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) {
96+
zend_throw_error(NULL, "Call to %s %s::__clone() from %s%s",
97+
zend_visibility_string(clone->common.fn_flags), ZSTR_VAL(clone->common.scope->name),
98+
scope ? "scope " : "global scope",
99+
scope ? ZSTR_VAL(scope->name) : ""
100+
);
101+
RETURN_THROWS();
102+
}
103+
}
104+
}
105+
106+
zend_object *cloned;
107+
cloned = zobj->handlers->clone_obj(zobj);
108+
109+
ZEND_ASSERT(cloned || EG(exception));
110+
if (EXPECTED(cloned)) {
111+
RETURN_OBJ(cloned);
112+
}
113+
}
114+
72115
ZEND_FUNCTION(exit)
73116
{
74117
zend_string *str = NULL;

Zend/zend_builtin_functions.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ class stdClass
77
{
88
}
99

10+
/** @refcount 1 */
11+
function _clone(object $object): object {}
12+
1013
function exit(string|int $status = 0): never {}
1114

1215
/** @alias exit */

Zend/zend_builtin_functions_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Zend/zend_compile.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4930,6 +4930,20 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args)
49304930
return SUCCESS;
49314931
}
49324932

4933+
static zend_result zend_compile_func_clone(znode *result, zend_ast_list *args)
4934+
{
4935+
znode arg_node;
4936+
4937+
if (args->children != 1) {
4938+
return FAILURE;
4939+
}
4940+
4941+
zend_compile_expr(&arg_node, args->child[0]);
4942+
zend_emit_op_tmp(result, ZEND_CLONE, &arg_node, NULL);
4943+
4944+
return SUCCESS;
4945+
}
4946+
49334947
static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *lcname, zend_ast_list *args, zend_function *fbc, uint32_t type) /* {{{ */
49344948
{
49354949
if (zend_string_equals_literal(lcname, "strlen")) {
@@ -4998,6 +5012,8 @@ static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *
49985012
return zend_compile_func_array_key_exists(result, args);
49995013
} else if (zend_string_equals_literal(lcname, "sprintf")) {
50005014
return zend_compile_func_sprintf(result, args);
5015+
} else if (zend_string_equals(lcname, ZSTR_KNOWN(ZEND_STR_CLONE))) {
5016+
return zend_compile_func_clone(result, args);
50015017
} else {
50025018
return FAILURE;
50035019
}
@@ -5391,17 +5407,6 @@ static void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */
53915407
}
53925408
/* }}} */
53935409

5394-
static void zend_compile_clone(znode *result, zend_ast *ast) /* {{{ */
5395-
{
5396-
zend_ast *obj_ast = ast->child[0];
5397-
5398-
znode obj_node;
5399-
zend_compile_expr(&obj_node, obj_ast);
5400-
5401-
zend_emit_op_tmp(result, ZEND_CLONE, &obj_node, NULL);
5402-
}
5403-
/* }}} */
5404-
54055410
static void zend_compile_global_var(zend_ast *ast) /* {{{ */
54065411
{
54075412
zend_ast *var_ast = ast->child[0];
@@ -11717,9 +11722,6 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
1171711722
case ZEND_AST_NEW:
1171811723
zend_compile_new(result, ast);
1171911724
return;
11720-
case ZEND_AST_CLONE:
11721-
zend_compile_clone(result, ast);
11722-
return;
1172311725
case ZEND_AST_ASSIGN_OP:
1172411726
zend_compile_compound_assign(result, ast);
1172511727
return;

0 commit comments

Comments
 (0)