Skip to content
Open
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
9 changes: 6 additions & 3 deletions compiler/pipes/convert-invoke-to-func-call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,11 @@ VertexPtr ConvertInvokeToFuncCallPass::on_clone(VertexAdaptor<op_clone> v_clone)
return call_virt_clone;
}

// clone $obj, when a class has the __clone() magic method, is replaced with { tmp = clone $obj; tmp->__clone(); $tmp }
if (klass->members.has_instance_method(ClassData::NAME_OF_CLONE)) {
// clone $obj, when a class (or any of its ancestors) has the __clone() magic method,
// is replaced with { tmp = clone $obj; tmp->__clone(); $tmp }
// find_instance_method_by_local_name walks the full parent_class chain, so an inherited
// __clone() from a base class is correctly invoked (fixes #57)
if (const auto *clone_method = klass->find_instance_method_by_local_name(ClassData::NAME_OF_CLONE)) {
auto tmp_var = VertexAdaptor<op_var>::create().set_location(v_clone);
tmp_var->str_val = gen_unique_name("tmp_for_clone");
tmp_var->extra_type = op_ex_var_superlocal;
Expand All @@ -130,7 +133,7 @@ VertexPtr ConvertInvokeToFuncCallPass::on_clone(VertexAdaptor<op_clone> v_clone)
auto call_magic_clone = VertexAdaptor<op_func_call>::create(tmp_var).set_location(v_clone);
call_magic_clone->str_val = ClassData::NAME_OF_CLONE;
call_magic_clone->extra_type = op_ex_func_call_arrow;
call_magic_clone->func_id = klass->members.get_instance_method(ClassData::NAME_OF_CLONE)->function;
call_magic_clone->func_id = clone_method->function;

return VertexAdaptor<op_seq_rval>::create(set_clone_to_tmp, call_magic_clone, tmp_var).set_location(v_clone);
}
Expand Down
29 changes: 29 additions & 0 deletions tests/phpt/clone_keyword/105_inherited_clone_method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@ok
<?php

// Regression test for https://github.com/VKCOM/kphp/issues/57
// When a child class does not define __clone() but a parent does,
// cloning an instance of the child must invoke the parent's __clone().

class Base {
public int $value;

public function __construct(int $v) {
$this->value = $v;
}

public function __clone() {
$this->value *= 2;
}
}

class Child extends Base {
// No __clone() here — must inherit Base::__clone()
}

$obj = new Child(5);
$copy = clone $obj;

// Base::__clone() doubles value: original stays 5, copy becomes 10
var_dump($obj->value); // int(5)
var_dump($copy->value); // int(10)
27 changes: 27 additions & 0 deletions tests/phpt/clone_keyword/106_deep_inheritance_clone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@ok
<?php

// Regression for #57: multi-level inheritance — __clone defined two levels up.

class GrandParent {
public string $log = '';

public function __clone() {
$this->log .= '+cloned';
}
}

class Parent_ extends GrandParent {
// no __clone
}

class Child extends Parent_ {
// no __clone either — must still reach GrandParent::__clone
}

$obj = new Child();
$obj->log = 'original';
$copy = clone $obj;

var_dump($obj->log); // string(8) "original"
var_dump($copy->log); // string(15) "original+cloned"