|
7 | 7 |
|
8 | 8 | #include "KotlinLambdaAnalyzer.h" |
9 | 9 |
|
| 10 | +#include <algorithm> |
10 | 11 | #include <gmock/gmock.h> |
11 | 12 | #include <gtest/gtest.h> |
12 | 13 |
|
13 | 14 | #include "Creators.h" |
| 15 | +#include "Debug.h" |
14 | 16 | #include "IRCode.h" |
15 | 17 | #include "RedexTest.h" |
16 | 18 |
|
@@ -157,33 +159,46 @@ class KotlinLambdaAnalyzerTest : public RedexTest { |
157 | 159 | return creator.create(); |
158 | 160 | } |
159 | 161 |
|
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. |
162 | 165 | static DexClass* create_lambda_with_synthetic_invoke() { |
163 | 166 | static unsigned counter = 0; |
164 | 167 | const auto type_name = |
165 | 168 | "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 | + } |
173 | 184 |
|
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) |
180 | 197 | ->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); |
182 | 199 | 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); |
187 | 202 | } |
188 | 203 |
|
189 | 204 | // Helper to create a well-formed Kotlin lambda class with a proper invoke |
@@ -372,11 +387,34 @@ TEST_F(KotlinLambdaAnalyzerTest, GetInvokeMethod_NonPublicInvoke) { |
372 | 387 | EXPECT_THAT(analyzer->get_invoke_method(), IsNull()); |
373 | 388 | } |
374 | 389 |
|
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. |
376 | 395 | const auto* lambda_class = create_lambda_with_synthetic_invoke(); |
377 | 396 | auto analyzer = KotlinLambdaAnalyzer::for_class(lambda_class); |
378 | 397 | 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"; |
380 | 418 | } |
381 | 419 |
|
382 | 420 | // Tests for lambda detection (KotlinLambdaAnalyzer::for_class) |
|
0 commit comments