Skip to content

Clone with #9497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
break;
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_OBJ_REF:
case ZEND_CLONE_INIT_PROP:
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
Expand Down
43 changes: 41 additions & 2 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2562,7 +2562,8 @@ static zend_always_inline zend_result _zend_update_type_info(
|| opline->opcode == ZEND_ASSIGN_OBJ_OP
|| opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
|| opline->opcode == ZEND_ASSIGN_DIM
|| opline->opcode == ZEND_ASSIGN_OBJ)
|| opline->opcode == ZEND_ASSIGN_OBJ
|| opline->opcode == ZEND_CLONE_INIT_PROP)
&& !(OP1_DATA_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS)) /*&& 0*/)) {
tmp = 0;
if (ssa_op->result_def >= 0 && !(ssa_var_info[ssa_op->result_def].type & MAY_BE_REF)) {
Expand All @@ -2578,7 +2579,8 @@ static zend_always_inline zend_result _zend_update_type_info(
|| opline->opcode == ZEND_ASSIGN_OBJ_OP
|| opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
|| opline->opcode == ZEND_ASSIGN_DIM
|| opline->opcode == ZEND_ASSIGN_OBJ) {
|| opline->opcode == ZEND_ASSIGN_OBJ
|| opline->opcode == ZEND_CLONE_INIT_PROP) {
if ((ssa_op+1)->op1_def >= 0 && !(ssa_var_info[(ssa_op+1)->op1_def].type & MAY_BE_REF)) {
UPDATE_SSA_TYPE(tmp, (ssa_op+1)->op1_def);
}
Expand Down Expand Up @@ -3028,6 +3030,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
break;
case ZEND_ASSIGN_OBJ:
case ZEND_CLONE_INIT_PROP:
if (opline->op1_type == IS_CV) {
zend_class_entry *ce = ssa_var_info[ssa_op->op1_use].ce;
bool add_rc = (t1 & (MAY_BE_OBJECT|MAY_BE_REF)) && (!ce
Expand Down Expand Up @@ -3695,6 +3698,7 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_OBJ_OP:
case ZEND_ASSIGN_OBJ_REF:
case ZEND_CLONE_INIT_PROP:
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
Expand Down Expand Up @@ -5237,6 +5241,41 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
}
}
return 1;
case ZEND_CLONE_INIT_PROP:
if (t1 & (MAY_BE_ANY-MAY_BE_OBJECT)) {
return 1;
}
if (ssa_op->op1_use) {
zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
zend_class_entry *ce = var_info->ce;

if (var_info->is_instanceof ||
!ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
return 1;
}

if (opline->op2_type != IS_CONST) {
return 1;
}

zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op2));
if (ZSTR_LEN(prop_name) > 0 && ZSTR_VAL(prop_name)[0] == '\0') {
return 1;
}

zend_property_info *prop_info =
zend_hash_find_ptr(&ce->properties_info, prop_name);
if (prop_info) {
if (ZEND_TYPE_IS_SET(prop_info->type)) {
return 1;
}
return !(prop_info->flags & ZEND_ACC_PUBLIC)
&& prop_info->ce != op_array->scope;
} else {
return !(ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
}
}
return 1;
case ZEND_ROPE_INIT:
case ZEND_ROPE_ADD:
case ZEND_ROPE_END:
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ bool zend_optimizer_update_op2_const(zend_op_array *op_array,
break;
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_OBJ_REF:
case ZEND_CLONE_INIT_PROP:
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
break;
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
case ZEND_CLONE_INIT_PROP:
next = opline + 1;
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
Expand Down
16 changes: 16 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Test that the property initializer list is required when "with" is given
--FILE--
<?php

class Foo
{
public function withProperties()
{
return clone $this with;
}
}

?>
--EXPECTF--
Parse error: syntax error, unexpected token ";", expecting "[" in %s on line %d
32 changes: 32 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error10.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Test that clone with cannot be used on public readonly properties from outside the class.
--FILE--
<?php

class Foo
{
public readonly int $baz;

public function __construct(
public readonly int $bar
) {}
}

$foo1 = new Foo(0);

try {
$foo2 = clone $foo1 with ["bar" => 1];
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}

try {
$foo2 = clone $foo1 with ["baz" => 1];
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}

?>
--EXPECT--
Cannot modify readonly property Foo::$bar from global scope
Cannot initialize readonly property Foo::$baz from global scope
38 changes: 38 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error11.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Test that the destructor of the cloned object is run
--FILE--
<?php

class Foo
{
public function __construct(
public readonly int $bar
) {}

public function __destruct()
{
echo "Destruct $this->bar\n";
}

public function with()
{
return clone $this with [
"bar" => 1,
"bar" => 2,
];
}
}

$foo = new Foo(0);

try {
$foo->with();
} catch (Error $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECTF--
Destruct 1
Cannot modify readonly property Foo::$bar
Destruct 0
16 changes: 16 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Test that the property initializer list cannot contain invalid identifiers when using the : syntax
--FILE--
<?php

class Foo
{
public function withProperties()
{
return clone $this with [1: "value"];
}
}

?>
--EXPECTF--
Parse error: syntax error, unexpected token ":" in %s on line %d
21 changes: 21 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Test that the clone property initializer respects visibility
--FILE--
<?php

class Foo
{
private $bar;
}

$foo = new Foo();

try {
$foo = clone $foo with ["bar" => 1];
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}

?>
--EXPECT--
Cannot access private property Foo::$bar
38 changes: 38 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error4.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Test that readonly properties cannot be assigned in "clone with" multiple times
--FILE--
<?php

class Foo
{
public function __construct(
public readonly int $bar
) {}

public function with()
{
return clone $this with [
"bar" => 1,
"bar" => 2,
];
}
}

$foo = new Foo(0);

try {
$foo->with();
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}

try {
$foo->with();
} catch (Error $exception) {
echo $exception->getMessage() . "\n";
}

?>
--EXPECT--
Cannot modify readonly property Foo::$bar
Cannot modify readonly property Foo::$bar
31 changes: 31 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error5.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Test TypeError when "cloning with" non-readonly property
--FILE--
<?php

class Foo
{
public int $bar = 1;
}

$obj1 = new Foo();

try {
clone $obj1 with ["bar" => []];
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}

try {
clone $obj1 with ["bar" => []]; // The same as above but now using cache slots
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}

var_dump($obj1->bar);

?>
--EXPECT--
Cannot assign array to property Foo::$bar of type int
Cannot assign array to property Foo::$bar of type int
int(1)
34 changes: 34 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error6.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Test "clone with" with a declared untyped property of an object throwing an exception in the destructor
--FILE--
<?php

class Foo
{
public $bar;

public function __destruct() {
throw new Exception("Error in destructor");
}
}

function returnFoo() {
return new Foo();
}

try {
clone returnFoo() with ["bar" => new stdClass()];
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}

try {
clone returnFoo() with ["bar" => new stdClass()]; // The same as above but now using cache slots
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECT--
Error in destructor
Error in destructor
33 changes: 33 additions & 0 deletions Zend/tests/clone_initializer/clone_initializer_error7.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Test "clone with" with dynamic properties of an object throwing an exception in the destructor
--FILE--
<?php

#[AllowDynamicProperties]
class Foo
{
public function __destruct() {
throw new Exception("Error in destructor");
}
}

function returnFoo() {
return new Foo();
}

try {
clone returnFoo() with ["bar" => new stdClass()];
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}

try {
clone returnFoo() with ["bar" => new stdClass()]; // The same as above but now using cache slots
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECT--
Error in destructor
Error in destructor
Loading