Skip to content

Commit ac0369a

Browse files
committed
Clone function
1 parent 2a621a5 commit ac0369a

15 files changed

+150
-20
lines changed

Zend/tests/clone/ast.phpt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ try {
4242

4343
?>
4444
--EXPECT--
45-
assert(false && ($y = clone($x)))
46-
assert(false && ($y = clone($x)))
47-
assert(false && ($y = clone($x, foo: $foo, bar: $bar)))
48-
assert(false && ($y = clone($x, ...$array)))
49-
assert(false && ($y = clone($x, ...['foo' => $foo, 'bar' => $bar])))
45+
assert(false && ($y = \clone($x)))
46+
assert(false && ($y = \clone($x)))
47+
assert(false && ($y = \clone($x, foo: $foo, bar: $bar)))
48+
assert(false && ($y = \clone($x, ...$array)))
49+
assert(false && ($y = \clone($x, ...['foo' => $foo, 'bar' => $bar])))

Zend/tests/clone/bug36071.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ $a = clone 0;
88
$a[0]->b = 0;
99
?>
1010
--EXPECTF--
11-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug36071.php:2
11+
Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, int given in %s:%d
1212
Stack trace:
13-
#0 {main}
13+
#0 %s(%d): clone(0)
14+
#1 {main}
1415
thrown in %sbug36071.php on line 2

Zend/tests/clone/bug42817.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ $a = clone(null);
66
array_push($a->b, $c);
77
?>
88
--EXPECTF--
9-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug42817.php:2
9+
Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, null given in %s:%d
1010
Stack trace:
11-
#0 {main}
11+
#0 %s(%d): clone(NULL)
12+
#1 {main}
1213
thrown in %sbug42817.php on line 2

Zend/tests/clone/bug42818.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Bug #42818 ($foo = clone(array()); leaks memory)
55
$foo = clone(array());
66
?>
77
--EXPECTF--
8-
Fatal error: Uncaught Error: __clone method called on non-object in %sbug42818.php:2
8+
Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, array given in %s:%d
99
Stack trace:
10-
#0 {main}
10+
#0 %s(%d): clone(Array)
11+
#1 {main}
1112
thrown in %sbug42818.php on line 2

Zend/tests/clone/clone_001.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ $a = clone array();
77

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

Zend/tests/clone/clone_003.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ $a = clone $b;
99
--EXPECTF--
1010
Warning: Undefined variable $b in %s on line %d
1111

12-
Fatal error: Uncaught Error: __clone method called on non-object in %s:%d
12+
Fatal error: Uncaught TypeError: clone(): Argument #1 ($object) must be of type object, null given in %s:%d
1313
Stack trace:
14-
#0 {main}
14+
#0 %s(%d): clone(NULL)
15+
#1 {main}
1516
thrown in %s on line %d

Zend/tests/clone/clone_with_001.phpt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ $array = [
1212
'arra' => [1, 2, 3],
1313
];
1414

15+
function gen() {
16+
yield 'from_gen' => 'value';
17+
}
18+
1519
var_dump(clone $x);
1620
var_dump(clone($x));
1721
var_dump(clone($x, foo: $foo, bar: $bar));
@@ -23,6 +27,8 @@ var_dump(clone($x, ...[
2327
"def",
2428
]));
2529

30+
var_dump(clone($x, ...gen()));
31+
2632
?>
2733
--EXPECTF--
2834
object(stdClass)#%d (0) {
@@ -59,3 +65,7 @@ object(stdClass)#%d (2) {
5965
["1"]=>
6066
string(3) "def"
6167
}
68+
object(stdClass)#%d (1) {
69+
["from_gen"]=>
70+
string(5) "value"
71+
}

Zend/tests/clone/clone_with_006.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ try {
1313

1414
?>
1515
--EXPECT--
16-
TypeError: Only arrays can be unpacked for clone, int given
16+
TypeError: Only arrays and Traversables can be unpacked, int given
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Clone with error cases
3+
--FILE--
4+
<?php
5+
6+
readonly class Clazz {
7+
public function __construct(
8+
public public(set) string $a,
9+
public public(set) string $b,
10+
) { }
11+
}
12+
13+
$c = new Clazz('default', 'default');
14+
15+
var_dump(clone($c, a: "updated A"));
16+
17+
?>
18+
--EXPECTF--
19+
Fatal error: Uncaught Error: Cannot modify readonly property Clazz::$a in %s:%d
20+
Stack trace:
21+
#0 %s(%d): clone(Object(Clazz), a: 'updated A')
22+
#1 {main}
23+
thrown in %s on line %d

Zend/zend_builtin_functions.c

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

72+
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_wrong_clone_call(zend_function *clone, zend_class_entry *scope)
73+
{
74+
zend_throw_error(NULL, "Call to %s %s::__clone() from %s%s",
75+
zend_visibility_string(clone->common.fn_flags), ZSTR_VAL(clone->common.scope->name),
76+
scope ? "scope " : "global scope",
77+
scope ? ZSTR_VAL(scope->name) : ""
78+
);
79+
}
80+
81+
ZEND_FUNCTION(clone)
82+
{
83+
zend_object *zobj;
84+
zval *args;
85+
uint32_t argc;
86+
HashTable *named_params;
87+
88+
ZEND_PARSE_PARAMETERS_START(1, -1)
89+
Z_PARAM_OBJ(zobj)
90+
Z_PARAM_VARIADIC_WITH_NAMED(args, argc, named_params);
91+
ZEND_PARSE_PARAMETERS_END();
92+
93+
zend_class_entry *scope = zend_get_executed_scope();
94+
95+
zval *obj;
96+
zend_class_entry *ce;
97+
zend_function *clone;
98+
zend_object_clone_obj_t clone_call;
99+
100+
ce = zobj->ce;
101+
clone = ce->clone;
102+
clone_call = zobj->handlers->clone_obj;
103+
if (UNEXPECTED(clone_call == NULL)) {
104+
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name));
105+
RETURN_THROWS();
106+
}
107+
108+
if (clone && !(clone->common.fn_flags & ZEND_ACC_PUBLIC)) {
109+
if (clone->common.scope != scope) {
110+
if (UNEXPECTED(clone->common.fn_flags & ZEND_ACC_PRIVATE)
111+
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) {
112+
zend_wrong_clone_call(clone, scope);
113+
RETURN_THROWS();
114+
}
115+
}
116+
}
117+
118+
zend_object *cloned = clone_call(zobj);
119+
120+
for (uint32_t i = 0; i < argc; i++) {
121+
zend_string *key = zend_long_to_str(i);
122+
123+
zend_update_property_ex(scope, cloned, key, &args[i]);
124+
}
125+
if (named_params != NULL) {
126+
zend_string *key;
127+
zval *val;
128+
ZEND_HASH_FOREACH_STR_KEY_VAL(named_params, key, val) {
129+
ZEND_ASSERT(key != NULL);
130+
131+
zend_update_property_ex(scope, cloned, key, val);
132+
133+
if (UNEXPECTED(EG(exception))) {
134+
OBJ_RELEASE(cloned);
135+
RETURN_THROWS();
136+
}
137+
} ZEND_HASH_FOREACH_END();
138+
}
139+
140+
RETURN_OBJ(cloned);
141+
}
142+
72143
ZEND_FUNCTION(exit)
73144
{
74145
zend_string *str = NULL;

0 commit comments

Comments
 (0)