Skip to content

Commit 50c805f

Browse files
xuhdevmeta-codesync[bot]
authored andcommitted
Accept synthetic bridge invoke in get_invoke_method()
Summary: `get_invoke_method()` previously required invoke methods to be non-synthetic, rejecting lambdas whose only invoke is a `bridge synthetic` method. Earlier Redex passes can inline the typed invoke into the synthetic bridge, leaving the bridge as the sole invoke method. The fix prefers the non-synthetic (typed) invoke when available, and falls back to the synthetic bridge when no non-synthetic invoke exists. Reviewed By: thezhangwei Differential Revision: D93177177 fbshipit-source-id: 545a63c6cc0dd7a10b45d82b6778b8791a162df9
1 parent 5965079 commit 50c805f

File tree

2 files changed

+80
-29
lines changed

2 files changed

+80
-29
lines changed

libredex/KotlinLambdaAnalyzer.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,31 @@ bool KotlinLambdaAnalyzer::is_trivial(size_t max_instructions) const {
6262
}
6363

6464
DexMethod* KotlinLambdaAnalyzer::get_invoke_method() const {
65-
DexMethod* result = nullptr;
65+
DexMethod* non_synthetic = nullptr;
66+
DexMethod* synthetic = nullptr;
6667
for (auto* method : m_cls->get_vmethods()) {
67-
if (method->get_name()->str() == "invoke" && is_public(method) &&
68-
!is_synthetic(method) && method->get_code() != nullptr) {
69-
if (result != nullptr) {
70-
// Multiple invoke methods found, ill-formed lambda.
68+
if (method->get_name()->str() != "invoke" || !is_public(method) ||
69+
method->get_code() == nullptr) {
70+
continue;
71+
}
72+
if (is_synthetic(method)) {
73+
if (synthetic != nullptr) {
74+
// Multiple synthetic invoke methods found, ill-formed lambda.
75+
return nullptr;
76+
}
77+
synthetic = method;
78+
} else {
79+
if (non_synthetic != nullptr) {
80+
// Multiple non-synthetic invoke methods found, ill-formed lambda.
7181
return nullptr;
7282
}
73-
result = method;
83+
non_synthetic = method;
7484
}
7585
}
76-
return result;
86+
// Prefer the non-synthetic (typed) invoke when available. Fall back to the
87+
// synthetic bridge invoke when earlier passes have inlined the typed invoke
88+
// into the bridge, leaving the bridge as the sole invoke method.
89+
return non_synthetic != nullptr ? non_synthetic : synthetic;
7790
}
7891

7992
DexField* KotlinLambdaAnalyzer::get_singleton_field() const {

test/unit/KotlinLambdaAnalyzerTest.cpp

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
#include "KotlinLambdaAnalyzer.h"
99

10+
#include <algorithm>
1011
#include <gmock/gmock.h>
1112
#include <gtest/gtest.h>
1213

1314
#include "Creators.h"
15+
#include "Debug.h"
1416
#include "IRCode.h"
1517
#include "RedexTest.h"
1618

@@ -157,33 +159,46 @@ class KotlinLambdaAnalyzerTest : public RedexTest {
157159
return creator.create();
158160
}
159161

160-
// Helper to create an ill-formed Kotlin lambda class with a synthetic invoke
161-
// method.
162+
// Helper to create a Kotlin lambda class whose only invoke method is a
163+
// synthetic bridge. Simulates what happens when earlier passes inline the
164+
// typed invoke into the bridge.
162165
static DexClass* create_lambda_with_synthetic_invoke() {
163166
static unsigned counter = 0;
164167
const auto type_name =
165168
"LLambdaAnalyzerSyntheticInvoke$" + std::to_string(counter++) + ";";
166-
auto* lambda_type = DexType::make_type(type_name);
167-
auto* kotlin_function_type =
168-
DexType::make_type("Lkotlin/jvm/functions/Function0;");
169-
170-
ClassCreator creator(lambda_type);
171-
creator.set_super(type::kotlin_jvm_internal_Lambda());
172-
creator.add_interface(kotlin_function_type);
169+
auto* cls = create_non_capturing_lambda(type_name, /*arity=*/0);
170+
// Remove the non-synthetic invoke that create_non_capturing_lambda added,
171+
// then add a synthetic bridge invoke in its place.
172+
auto vmethods = cls->get_vmethods();
173+
for (auto* m : vmethods) {
174+
cls->remove_method(m);
175+
}
176+
add_synthetic_bridge_invoke(cls, /*arity=*/0);
177+
// Verify: no non-synthetic invoke should remain.
178+
always_assert(
179+
std::ranges::none_of(cls->get_vmethods(), [](const DexMethod* m) {
180+
return m->get_name()->str() == "invoke" && !is_synthetic(m);
181+
}));
182+
return cls;
183+
}
173184

174-
// Add a synthetic invoke method
175-
auto* invoke_proto = DexProto::make_proto(type::java_lang_Object(),
176-
DexTypeList::make_type_list({}));
177-
auto* invoke_method =
178-
DexMethod::make_method(lambda_type, DexString::make_string("invoke"),
179-
invoke_proto)
185+
// Add a synthetic bridge invoke method (type-erased) to a lambda class.
186+
static void add_synthetic_bridge_invoke(DexClass* cls, size_t arity) {
187+
DexTypeList::ContainerType param_types;
188+
for (size_t i = 0; i < arity; ++i) {
189+
param_types.push_back(type::java_lang_Object());
190+
}
191+
auto* bridge_proto = DexProto::make_proto(
192+
type::java_lang_Object(),
193+
DexTypeList::make_type_list(std::move(param_types)));
194+
auto* bridge_method =
195+
DexMethod::make_method(cls->get_type(),
196+
DexString::make_string("invoke"), bridge_proto)
180197
->make_concrete(ACC_PUBLIC | ACC_SYNTHETIC, true);
181-
auto code = std::make_unique<IRCode>(invoke_method, 1);
198+
auto code = std::make_unique<IRCode>(bridge_method, 1 + arity);
182199
code->push_back(new IRInstruction(OPCODE_RETURN_OBJECT));
183-
invoke_method->set_code(std::move(code));
184-
creator.add_method(invoke_method);
185-
186-
return creator.create();
200+
bridge_method->set_code(std::move(code));
201+
cls->add_method(bridge_method);
187202
}
188203

189204
// Helper to create a well-formed Kotlin lambda class with a proper invoke
@@ -372,11 +387,34 @@ TEST_F(KotlinLambdaAnalyzerTest, GetInvokeMethod_NonPublicInvoke) {
372387
EXPECT_THAT(analyzer->get_invoke_method(), IsNull());
373388
}
374389

375-
TEST_F(KotlinLambdaAnalyzerTest, GetInvokeMethod_SyntheticInvoke) {
390+
TEST_F(KotlinLambdaAnalyzerTest,
391+
GetInvokeMethod_ReturnsSyntheticInvokeWhenNoNonSynthetic) {
392+
// When earlier passes inline the typed invoke into the synthetic bridge,
393+
// the bridge becomes the sole invoke method. get_invoke_method() should
394+
// fall back to it.
376395
const auto* lambda_class = create_lambda_with_synthetic_invoke();
377396
auto analyzer = KotlinLambdaAnalyzer::for_class(lambda_class);
378397
ASSERT_TRUE(analyzer.has_value());
379-
EXPECT_THAT(analyzer->get_invoke_method(), IsNull());
398+
DexMethod* invoke = analyzer->get_invoke_method();
399+
ASSERT_THAT(invoke, NotNull());
400+
EXPECT_EQ(invoke->str(), "invoke");
401+
EXPECT_TRUE(is_synthetic(invoke));
402+
}
403+
404+
TEST_F(KotlinLambdaAnalyzerTest,
405+
GetInvokeMethod_PrefersNonSyntheticOverSynthetic) {
406+
// When both a typed (non-synthetic) invoke and a synthetic bridge exist,
407+
// the typed one should be preferred.
408+
auto* lambda_class =
409+
create_non_capturing_lambda("LLambdaAnalyzerBothInvoke$0;", /*arity=*/1);
410+
add_synthetic_bridge_invoke(lambda_class, /*arity=*/0);
411+
412+
auto analyzer = KotlinLambdaAnalyzer::for_class(lambda_class);
413+
ASSERT_TRUE(analyzer.has_value());
414+
DexMethod* invoke = analyzer->get_invoke_method();
415+
ASSERT_THAT(invoke, NotNull());
416+
EXPECT_FALSE(is_synthetic(invoke))
417+
<< "Should prefer the non-synthetic invoke method";
380418
}
381419

382420
// Tests for lambda detection (KotlinLambdaAnalyzer::for_class)

0 commit comments

Comments
 (0)