| // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.kotlin; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.ToolHelper.KotlinTargetVersion; |
| import com.android.tools.r8.code.NewInstance; |
| import com.android.tools.r8.code.SgetObject; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Streams; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import org.junit.Test; |
| |
| public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase { |
| |
| public KotlinClassInlinerTest( |
| KotlinTargetVersion targetVersion, boolean allowAccessModification) { |
| super(targetVersion, allowAccessModification); |
| } |
| |
| private static boolean isLambda(DexClass clazz) { |
| return !clazz.type.getPackageDescriptor().startsWith("kotlin") && |
| (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz)); |
| } |
| |
| private static boolean isKStyleLambdaOrGroup(DexClass clazz) { |
| return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;"); |
| } |
| |
| private static boolean isJStyleLambdaOrGroup(DexClass clazz) { |
| return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") && |
| clazz.interfaces.size() == 1; |
| } |
| |
| private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) { |
| Set<DexType> lambdaClasses = inspector.allClasses().stream() |
| .filter(clazz -> isLambda(clazz.getDexClass())) |
| .map(clazz -> clazz.getDexClass().type) |
| .collect(Collectors.toSet()); |
| return lambdaClasses::contains; |
| } |
| |
| @Test |
| public void testJStyleLambdas() throws Exception { |
| assumeTrue("Only work with -allowaccessmodification", allowAccessModification); |
| final String mainClassName = "class_inliner_lambda_j_style.MainKt"; |
| runTest( |
| "class_inliner_lambda_j_style", |
| mainClassName, |
| false, |
| app -> { |
| CodeInspector inspector = new CodeInspector(app); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), isPresent()); |
| }); |
| |
| runTest( |
| "class_inliner_lambda_j_style", |
| mainClassName, |
| true, |
| app -> { |
| CodeInspector inspector = new CodeInspector(app); |
| Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); |
| ClassSubject clazz = inspector.clazz(mainClassName); |
| |
| assertEquals( |
| Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateless")); |
| |
| assertEquals(Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), |
| not(isPresent())); |
| |
| assertEquals( |
| Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful2")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), |
| not(isPresent())); |
| |
| assertEquals( |
| Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful3")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), |
| not(isPresent())); |
| }); |
| } |
| |
| @Test |
| public void testKStyleLambdas() throws Exception { |
| assumeTrue("Only work with -allowaccessmodification", allowAccessModification); |
| final String mainClassName = "class_inliner_lambda_k_style.MainKt"; |
| runTest( |
| "class_inliner_lambda_k_style", |
| mainClassName, |
| false, |
| app -> { |
| CodeInspector inspector = new CodeInspector(app); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), |
| isPresent()); |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), |
| isPresent()); |
| }); |
| |
| runTest( |
| "class_inliner_lambda_k_style", |
| mainClassName, |
| true, |
| app -> { |
| CodeInspector inspector = new CodeInspector(app); |
| Predicate<DexType> lambdaCheck = createLambdaCheck(inspector); |
| ClassSubject clazz = inspector.clazz(mainClassName); |
| |
| assertEquals( |
| Sets.newHashSet(), |
| collectAccessedTypes( |
| lambdaCheck, clazz, "testKotlinSequencesStateless", "kotlin.sequences.Sequence")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"), |
| not(isPresent())); |
| |
| assertEquals( |
| Sets.newHashSet(), |
| collectAccessedTypes( |
| lambdaCheck, |
| clazz, |
| "testKotlinSequencesStateful", |
| "int", |
| "int", |
| "kotlin.sequences.Sequence")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"), |
| not(isPresent())); |
| |
| assertEquals( |
| Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod")); |
| |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"), |
| not(isPresent())); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"), |
| not(isPresent())); |
| assertThat( |
| inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"), |
| not(isPresent())); |
| |
| assertEquals( |
| Sets.newHashSet(), |
| collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda")); |
| |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"), |
| not(isPresent())); |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"), |
| not(isPresent())); |
| assertThat( |
| inspector.clazz( |
| "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"), |
| not(isPresent())); |
| }); |
| } |
| |
| @Test |
| public void testDataClass() throws Exception { |
| assumeTrue("Only work with -allowaccessmodification", allowAccessModification); |
| final String mainClassName = "class_inliner_data_class.MainKt"; |
| runTest( |
| "class_inliner_data_class", |
| mainClassName, |
| true, |
| app -> { |
| CodeInspector inspector = new CodeInspector(app); |
| ClassSubject clazz = inspector.clazz(mainClassName); |
| assertTrue( |
| collectAccessedTypes( |
| type -> !type.toSourceString().startsWith("java."), |
| clazz, |
| "main", |
| String[].class.getCanonicalName()) |
| .isEmpty()); |
| assertEquals( |
| Lists.newArrayList( |
| "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"), |
| collectStaticCalls(clazz, "main", String[].class.getCanonicalName())); |
| }); |
| } |
| |
| private Set<String> collectAccessedTypes(Predicate<DexType> isTypeOfInterest, |
| ClassSubject clazz, String methodName, String... params) { |
| assertNotNull(clazz); |
| MethodSignature signature = new MethodSignature(methodName, "void", params); |
| DexCode code = clazz.method(signature).getMethod().getCode().asDexCode(); |
| return Stream.concat( |
| filterInstructionKind(code, NewInstance.class) |
| .map(insn -> ((NewInstance) insn).getType()), |
| filterInstructionKind(code, SgetObject.class) |
| .map(insn -> insn.getField().holder) |
| ) |
| .filter(isTypeOfInterest) |
| .map(DexType::toSourceString) |
| .collect(Collectors.toSet()); |
| } |
| |
| protected void runTest(String folder, String mainClass, |
| boolean enabled, AndroidAppInspector inspector) throws Exception { |
| runTest( |
| folder, |
| mainClass, |
| // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources |
| StringUtils.lines( |
| "-neverinline class * { void test*State*(...); }", |
| "-neverinline class * { void testBigExtraMethod(...); }", |
| "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"), |
| options -> { |
| options.enableInlining = true; |
| options.enableClassInlining = enabled; |
| options.enableLambdaMerging = false; |
| |
| // Tests check if specific lambdas are inlined or not, where some of target lambdas have |
| // at least 4 instructions. |
| options.inliningInstructionLimit = 4; |
| |
| // Class inlining depends on the processing order. We therefore insert all call graph |
| // edges and verify that we can class inline everything under this condition. |
| options.testing.addCallEdgesForLibraryInvokes = true; |
| }, |
| inspector); |
| } |
| |
| private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) { |
| assertNotNull(clazz); |
| MethodSignature signature = new MethodSignature(methodName, "void", params); |
| MethodSubject method = clazz.method(signature); |
| return Streams.stream(method.iterateInstructions(InstructionSubject::isInvokeStatic)) |
| .map(insn -> insn.getMethod().toSourceString()) |
| .sorted() |
| .collect(Collectors.toList()); |
| } |
| } |