Skip to content

Commit

Permalink
Fix compilation error for lambdas in unused functions (#1016)
Browse files Browse the repository at this point in the history
Previously, in certain cases, there could be a compilation error:
> var Lambda$ufa6f526451637135_0::$tmp_var is declared 
> but never written; please, provide a default value
  • Loading branch information
tolk-vm authored Jun 19, 2024
1 parent d630835 commit 3e2cdf6
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 6 deletions.
1 change: 0 additions & 1 deletion compiler/pipes/check-classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ inline void CheckClassesPass::check_static_fields_inited(ClassPtr klass) {
}

inline void CheckClassesPass::check_instance_fields_inited(ClassPtr klass) {
// TODO KPHP-221: the old code is kept for now (check for Unknown)
klass->members.for_each([](const ClassMemberInstanceField &f) {
PrimitiveType ptype = f.var->tinf_node.get_type()->get_real_ptype();
kphp_error(ptype != tp_any,
Expand Down
82 changes: 82 additions & 0 deletions compiler/pipes/filter-only-actually-used.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,78 @@
#include "compiler/data/src-file.h"
#include "compiler/compiler-core.h"
#include "compiler/threading/profiler.h"
#include "compiler/vertex-util.h"

// having a typed callable __invoke(), which is a virtual function with switch-case dispatching,
// replace body of `case {lambda_class_to_remove.hash()}:` (which is a lambda invoke call)
// to just `break`; see below, why this is important
class RemoveLambdaCallFromTypedCallablePass final : public FunctionPassBase {
std::string case_hash;

public:
std::string get_description() override {
return "Remove lambda call from typed callable";
}

explicit RemoveLambdaCallFromTypedCallablePass(ClassPtr lambda_class_to_remove)
: case_hash(std::to_string(lambda_class_to_remove->get_hash())) {}

VertexPtr on_enter_vertex(VertexPtr root) override {
if (auto as_case = root.try_as<op_case>()) {
if (auto as_int_const = as_case->expr().try_as<op_int_const>()) {
if (as_int_const->str_val == case_hash) {
auto level1 = VertexUtil::create_int_const(1);
return VertexAdaptor<op_case>::create(as_int_const, VertexAdaptor<op_seq>::create(VertexAdaptor<op_break>::create(level1)));
}
}
}

return root;
}
};

// when a lambda with `use` statement (=> with an instance field) occurs inside unused function,
// it still can be reachable from a typed callable __invoke(),
// but its field types can't be inferred, they'll be left 'any' and trigger an error after
// to prevent this, manually remove this lambda from that __invoke() body
// as well as remove all lambda's mentions from used_functions
class AnalyzeLambdasInUnusedFunctionPass final : public FunctionPassBase {
IdMap<FunctionPtr> &used_functions;

public:
std::string get_description() override {
return "Analyze lambdas in unused function";
}

explicit AnalyzeLambdasInUnusedFunctionPass(IdMap<FunctionPtr> &used_functions)
: used_functions(used_functions) {}

VertexPtr on_enter_vertex(VertexPtr root) override {
if (auto as_call = root.try_as<op_func_call>();
as_call && as_call->func_id->class_id && as_call->func_id->class_id->is_lambda_class() && as_call->func_id->is_constructor()) {
ClassPtr lambda_class = as_call->func_id->class_id;
const ClassMemberInstanceMethod *m_invoke = lambda_class->members.get_instance_method("__invoke");
if (m_invoke && used_functions[m_invoke->function->outer_function]) {
// f_lambda occurs inside unused function, but is used; the only reason is it's used from a typed callable
FunctionPtr f_lambda = m_invoke->function->outer_function;
kphp_assert(lambda_class->implements.size() == 1 && lambda_class->implements[0]->is_typed_callable_interface());
FunctionPtr f_typed_invoke = lambda_class->implements[0]->members.get_instance_method("__invoke")->function;

RemoveLambdaCallFromTypedCallablePass pass(lambda_class);
run_function_pass(f_typed_invoke->root, &pass);

AnalyzeLambdasInUnusedFunctionPass self_pass(used_functions);
run_function_pass(f_lambda, &self_pass);

used_functions[f_lambda] = {};
used_functions[m_invoke->function] = {};
used_functions[lambda_class->construct_function] = {};
}
}

return root;
}
};

namespace {

Expand Down Expand Up @@ -261,6 +333,16 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream<FunctionPtr> &os) {
remove_unused_class_methods(all, used_functions);
stage::die_if_global_errors();

// remove lambdas from unused functions, see comments above
for (const auto &f_and_e : all) {
FunctionPtr fun = f_and_e.first;
if (fun->has_lambdas_inside && !fun->is_lambda() && !used_functions[fun]) {
AnalyzeLambdasInUnusedFunctionPass pass(used_functions);
run_function_pass(fun, &pass);
}
}
stage::die_if_global_errors();

// forward the reachable functions into the data stream;
// this should be the last step
for (const auto &f : used_functions) {
Expand Down
6 changes: 1 addition & 5 deletions compiler/pipes/generate-virtual-methods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,6 @@ void generate_body_of_virtual_method(FunctionPtr virtual_function) {
cases.emplace_back(v_case);
}
}
if (!cases.empty()) {
auto case_default_warn = generate_critical_error_call(fmt_format("call method({}) on null object", virtual_function->as_human_readable(false)));
cases.emplace_back(VertexAdaptor<op_default>::create(VertexAdaptor<op_seq>::create(case_default_warn)));
}

if (cases.empty() && !stage::has_error()) {
// when there are no inheritors of an interface, generate an empty body if possible —
Expand All @@ -436,7 +432,7 @@ void generate_body_of_virtual_method(FunctionPtr virtual_function) {
auto call_get_hash = VertexAdaptor<op_func_call>::create(ClassData::gen_vertex_this({}));
call_get_hash->str_val = "get_hash_of_class";
call_get_hash->func_id = G->get_function(call_get_hash->str_val);
virtual_function->root->cmd_ref() = VertexAdaptor<op_seq>::create(VertexUtil::create_switch_vertex(virtual_function, call_get_hash, std::move(cases)));
virtual_function->root->cmd_ref() = VertexAdaptor<op_seq>::create(VertexUtil::create_switch_vertex(virtual_function, call_get_hash, std::move(cases)), generate_critical_error_call(fmt_format("call method({}) on null object", virtual_function->as_human_readable(false))));
}

virtual_function->type = FunctionData::func_local; // could be func_extern before, but now it has a body
Expand Down
61 changes: 61 additions & 0 deletions tests/phpt/lambdas/020_uses_in_lambda.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,64 @@ function l2_modif() {
}
(new WithFCapturingDeep)->l2();
(new WithFCapturingDeep)->l2_modif();

class Example020 {
static private function takeInt(int $i) { echo $i; }

public function unused_function(): void {
$tmp_var = "";
$this->call_function(function() use ($tmp_var): void {});
$int = 10;
$this->call_function(function() use ($int): void { self::takeInt($int); });
}

/** @param callable():void $fn */
public function call_function(callable $fn): void {
$fn();
}
}

(new Example020())->call_function(function(): void {});

class Bxample020 {
/**
* @param callable(int):int $fn
*/
public function unused_function(callable $fn): int {
return $this->test2(function () use ($fn) {
return $fn(1);
});
}

/**
* @param callable():int $fn
*/
public function test2(callable $fn): int {
return $fn();
}
}

$bxample = new Bxample020();
$bxample->test2(function() { return 0; });


class Cxample {
public function unused_function(): void {
$tmp_var = "";
$this->call_function_first(function() use ($tmp_var): void {
$this->call_function_second(function() use ($tmp_var) {});
});
}

/** @param callable():void $fn */
public function call_function_first(callable $fn): void {
$fn();
}

/** @param callable():void $fn */
public function call_function_second(callable $fn): void {
$fn();
}
}

(new Cxample)->call_function_first(function():void{} );

0 comments on commit 3e2cdf6

Please sign in to comment.