|  | // Copyright (c) 2020, 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.classmerging; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestParametersCollection; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexProgramClass; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.LookupResult; | 
|  | import com.android.tools.r8.graph.LookupResult.LookupResultSuccess; | 
|  | import com.android.tools.r8.graph.ResolutionResult; | 
|  | import com.android.tools.r8.shaking.AppInfoWithLiveness; | 
|  | import com.android.tools.r8.shaking.ProguardConfigurationRule; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import java.util.HashSet; | 
|  | import java.util.Set; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class KeptTargetsIncompleteDiamondTest extends TestBase { | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection data() { | 
|  | return getTestParameters().withNoneRuntime().build(); | 
|  | } | 
|  |  | 
|  | public KeptTargetsIncompleteDiamondTest(TestParameters parameters) { | 
|  | parameters.assertNoneRuntime(); | 
|  | } | 
|  |  | 
|  | private AppView<AppInfoWithLiveness> computeAppViewWithLiveness( | 
|  | Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception { | 
|  | return computeAppViewWithLiveness( | 
|  | buildClasses(I.class, J.class, K.class, L.class, A.class, Main.class) | 
|  | .addLibraryFile(ToolHelper.getJava8RuntimeJar()) | 
|  | .build(), | 
|  | factory -> | 
|  | buildConfigForRules( | 
|  | factory, | 
|  | ImmutableList.<ProguardConfigurationRule>builder() | 
|  | .addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory)) | 
|  | .addAll(buildKeepRuleForClass(classToBeKept, factory)) | 
|  | .addAll(buildKeepRuleForClassAndMethods(Main.class, factory)) | 
|  | .build())); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testRefinedReceiverOnStaticTarget() throws Exception { | 
|  | // I { <-- kept | 
|  | //   foo() <-- kept, initial | 
|  | // } | 
|  | // | 
|  | // J { | 
|  | //   default foo() { ... } <-- kept, resolved | 
|  | // } | 
|  | // | 
|  | // K {  } | 
|  | // | 
|  | // L extends I, K { } <-- upperbound | 
|  | // | 
|  | // A implements L { <-- lowerbound | 
|  | // | 
|  | // } | 
|  | AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class); | 
|  | DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory()); | 
|  | ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method); | 
|  | DexType typeI = buildType(I.class, appView.dexItemFactory()); | 
|  | DexType typeL = buildType(L.class, appView.dexItemFactory()); | 
|  | DexType typeA = buildType(A.class, appView.dexItemFactory()); | 
|  | DexProgramClass classI = appView.definitionForProgramType(typeI); | 
|  | DexProgramClass classL = appView.definitionForProgramType(typeL); | 
|  | DexProgramClass classA = appView.definitionForProgramType(typeA); | 
|  | LookupResult lookupResult = | 
|  | resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classL, classA); | 
|  | assertTrue(lookupResult.isLookupResultSuccess()); | 
|  | LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess(); | 
|  | Set<String> targets = new HashSet<>(); | 
|  | lookupResultSuccess.forEach( | 
|  | methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()), | 
|  | lambdaTarget -> { | 
|  | assert false; | 
|  | }); | 
|  | Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); | 
|  | assertEquals(expected, targets); | 
|  | assertTrue(lookupResultSuccess.isComplete()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testRefinedReceiverOnStaticTargetInRange() throws Exception { | 
|  | // I { <-- kept | 
|  | //   foo() <-- kept, initial, upperbound | 
|  | // } | 
|  | // | 
|  | // J { | 
|  | //   default foo() { ... } <-- kept, resolved | 
|  | // } | 
|  | // | 
|  | // K {  } | 
|  | // | 
|  | // L extends I, K { } | 
|  | // | 
|  | // A implements L { <-- lowerbound | 
|  | // | 
|  | // } | 
|  | AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class); | 
|  | DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory()); | 
|  | ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method); | 
|  | DexType typeI = buildType(I.class, appView.dexItemFactory()); | 
|  | DexType typeL = buildType(L.class, appView.dexItemFactory()); | 
|  | DexType typeA = buildType(A.class, appView.dexItemFactory()); | 
|  | DexProgramClass classI = appView.definitionForProgramType(typeI); | 
|  | DexProgramClass classA = appView.definitionForProgramType(typeA); | 
|  | LookupResult lookupResult = | 
|  | resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classI, classA); | 
|  | assertTrue(lookupResult.isLookupResultSuccess()); | 
|  | LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess(); | 
|  | Set<String> targets = new HashSet<>(); | 
|  | lookupResultSuccess.forEach( | 
|  | methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()), | 
|  | lambdaTarget -> { | 
|  | assert false; | 
|  | }); | 
|  | Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); | 
|  | assertEquals(expected, targets); | 
|  | assertTrue(lookupResultSuccess.isComplete()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testRefinedReceiverWithKeptDispatchTarget() throws Exception { | 
|  | // I { | 
|  | //   foo() <-- initial | 
|  | // } | 
|  | // | 
|  | // J { <-- kept | 
|  | //   default foo() { ... } <-- kept, resolved | 
|  | // } | 
|  | // | 
|  | // K {  } | 
|  | // | 
|  | // L extends I, K { } | 
|  | // | 
|  | // A implements L { <-- lowerbound, upperbound | 
|  | // | 
|  | // } | 
|  | AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, J.class); | 
|  | DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory()); | 
|  | ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method); | 
|  | DexType typeI = buildType(I.class, appView.dexItemFactory()); | 
|  | DexType typeB = buildType(A.class, appView.dexItemFactory()); | 
|  | DexProgramClass classI = appView.definitionForProgramType(typeI); | 
|  | DexProgramClass classA = appView.definitionForProgramType(typeB); | 
|  | LookupResult lookupResult = | 
|  | resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA); | 
|  | assertTrue(lookupResult.isLookupResultSuccess()); | 
|  | LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess(); | 
|  | Set<String> targets = new HashSet<>(); | 
|  | lookupResultSuccess.forEach( | 
|  | methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()), | 
|  | lambdaTarget -> { | 
|  | assert false; | 
|  | }); | 
|  | Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); | 
|  | assertEquals(expected, targets); | 
|  | assertTrue(lookupResultSuccess.isIncomplete()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testRefinedReceiverWithKeptRefinedReceiver() throws Exception { | 
|  | // I { | 
|  | //   foo() <-- initial | 
|  | // } | 
|  | // | 
|  | // J { | 
|  | //   default foo() { ... } <-- kept, resolved | 
|  | // } | 
|  | // | 
|  | // K {  } | 
|  | // | 
|  | // A implements I, K { <-- kept, lowerbound, upperbound | 
|  | // | 
|  | // } | 
|  | AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, A.class); | 
|  | DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory()); | 
|  | ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method); | 
|  | DexType typeI = buildType(I.class, appView.dexItemFactory()); | 
|  | DexType typeB = buildType(A.class, appView.dexItemFactory()); | 
|  | DexProgramClass classI = appView.definitionForProgramType(typeI); | 
|  | DexProgramClass classA = appView.definitionForProgramType(typeB); | 
|  | LookupResult lookupResult = | 
|  | resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA); | 
|  | assertTrue(lookupResult.isLookupResultSuccess()); | 
|  | LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess(); | 
|  | Set<String> targets = new HashSet<>(); | 
|  | lookupResultSuccess.forEach( | 
|  | methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()), | 
|  | lambdaTarget -> { | 
|  | assert false; | 
|  | }); | 
|  | Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); | 
|  | assertEquals(expected, targets); | 
|  | assertTrue(lookupResultSuccess.isIncomplete()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testRefinedReceiverWithKeptClassOnInitial() throws Exception { | 
|  | // I { <-- kept | 
|  | //   foo() <-- kept, initial | 
|  | // } | 
|  | // | 
|  | // J { | 
|  | //   default foo() { ... } <-- resolved | 
|  | // } | 
|  | // | 
|  | // K {  } | 
|  | // | 
|  | // L extends I, K { } | 
|  | // | 
|  | // A implements L { <-- lowerbound, upperbound | 
|  | // | 
|  | // } | 
|  | AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class); | 
|  | DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory()); | 
|  | ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method); | 
|  | DexType typeI = buildType(I.class, appView.dexItemFactory()); | 
|  | DexType typeB = buildType(A.class, appView.dexItemFactory()); | 
|  | DexProgramClass classI = appView.definitionForProgramType(typeI); | 
|  | DexProgramClass classA = appView.definitionForProgramType(typeB); | 
|  | LookupResult lookupResult = | 
|  | resolutionResult.lookupVirtualDispatchTargets(classI, appView.appInfo(), classA, classA); | 
|  | assertTrue(lookupResult.isLookupResultSuccess()); | 
|  | LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess(); | 
|  | Set<String> targets = new HashSet<>(); | 
|  | lookupResultSuccess.forEach( | 
|  | methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()), | 
|  | lambdaTarget -> { | 
|  | assert false; | 
|  | }); | 
|  | Set<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo"); | 
|  | assertEquals(expected, targets); | 
|  | assertTrue(lookupResultSuccess.isComplete()); | 
|  | } | 
|  |  | 
|  | public interface I { | 
|  | void foo(); | 
|  | } | 
|  |  | 
|  | public interface J extends I { | 
|  | @Override | 
|  | default void foo() { | 
|  | System.out.println("K.foo"); | 
|  | } | 
|  | } | 
|  |  | 
|  | public interface K extends J {} | 
|  |  | 
|  | public interface L extends I, K {} | 
|  |  | 
|  | public static class A implements L {} | 
|  |  | 
|  | public static class Main { | 
|  |  | 
|  | public static void main(String[] args) { | 
|  | new A(); | 
|  | } | 
|  | } | 
|  | } |