| // 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.resolution; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertTrue; | |
| import com.android.tools.r8.AsmTestBase; | |
| import com.android.tools.r8.graph.AppInfo; | |
| import com.android.tools.r8.graph.AppView; | |
| import com.android.tools.r8.graph.DexEncodedMethod; | |
| import com.android.tools.r8.graph.DexMethod; | |
| import com.android.tools.r8.graph.DexType; | |
| import com.android.tools.r8.graph.LookupResult; | |
| import com.android.tools.r8.graph.ResolutionResult; | |
| import com.android.tools.r8.resolution.singletarget.Main; | |
| import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass; | |
| import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass; | |
| import com.android.tools.r8.resolution.singletarget.one.InterfaceWithDefault; | |
| import com.android.tools.r8.resolution.singletarget.one.IrrelevantInterfaceWithDefaultDump; | |
| import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne; | |
| import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree; | |
| import com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo; | |
| import com.android.tools.r8.resolution.singletarget.three.ThirdAbstractTopClass; | |
| import com.android.tools.r8.resolution.singletarget.three.ThirdSubClassOne; | |
| import com.android.tools.r8.resolution.singletarget.three.ThirdSubClassTwoDump; | |
| import com.android.tools.r8.resolution.singletarget.two.OtherAbstractSubClassOne; | |
| import com.android.tools.r8.resolution.singletarget.two.OtherAbstractSubClassTwo; | |
| import com.android.tools.r8.resolution.singletarget.two.OtherAbstractTopClass; | |
| import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassOne; | |
| import com.android.tools.r8.resolution.singletarget.two.OtherSubSubClassTwo; | |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; | |
| import com.google.common.collect.ImmutableList; | |
| import java.io.IOException; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| import org.junit.Assert; | |
| import org.junit.BeforeClass; | |
| 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 SingleTargetLookupTest extends AsmTestBase { | |
| /** Initialized in @Before rule. */ | |
| public static AppView<AppInfoWithLiveness> appView; | |
| public static AppInfoWithLiveness appInfo; | |
| public static List<Class<?>> CLASSES = | |
| ImmutableList.of( | |
| InterfaceWithDefault.class, | |
| AbstractTopClass.class, | |
| AbstractSubClass.class, | |
| SubSubClassOne.class, | |
| SubSubClassTwo.class, | |
| SubSubClassThree.class, | |
| OtherAbstractTopClass.class, | |
| OtherAbstractSubClassOne.class, | |
| OtherAbstractSubClassTwo.class, | |
| OtherSubSubClassOne.class, | |
| OtherSubSubClassTwo.class, | |
| ThirdAbstractTopClass.class, | |
| ThirdSubClassOne.class, | |
| Main.class); | |
| public static List<byte[]> ASM_CLASSES = ImmutableList.of( | |
| getBytesFromAsmClass(IrrelevantInterfaceWithDefaultDump::dump), | |
| getBytesFromAsmClass(ThirdSubClassTwoDump::dump) | |
| ); | |
| public SingleTargetLookupTest( | |
| String methodName, | |
| Class invokeReceiver, | |
| Class singleTargetHolderOrNull, | |
| List<Class> virtualTargetHolders) { | |
| this.methodName = methodName; | |
| this.invokeReceiver = invokeReceiver; | |
| this.singleTargetHolderOrNull = singleTargetHolderOrNull; | |
| this.virtualTargetHolders = virtualTargetHolders; | |
| } | |
| @BeforeClass | |
| public static void computeAppInfo() throws Exception { | |
| appView = computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class); | |
| appInfo = appView.appInfo(); | |
| } | |
| private static Object[] noVirtualSingleTarget(String name, Class<?> receiverAndTarget) { | |
| return new Object[] {name, receiverAndTarget, null, Collections.emptyList()}; | |
| } | |
| private static Object[] singleTarget(String name, Class<?> receiverAndTarget) { | |
| return new Object[]{name, receiverAndTarget, receiverAndTarget, | |
| Collections.singletonList(receiverAndTarget)}; | |
| } | |
| private static Object[] singleTarget(String name, Class<?> receiver, Class<?> target) { | |
| return new Object[]{name, receiver, target, Collections.singletonList(target)}; | |
| } | |
| private static Object[] singleTargetWithAbstracts(String name, Class<?> receiver, | |
| Class<?> target, Class<?>... extras) { | |
| return new Object[]{name, receiver, target, | |
| ImmutableList.builder().add(target).add((Object[]) extras).build()}; | |
| } | |
| private static Object[] manyTargets(String name, Class<?> receiver, Class<?>... targets) { | |
| return new Object[]{name, receiver, null, ImmutableList.copyOf(targets)}; | |
| } | |
| private static Object[] onlyUnreachableTargets(String name, Class<?> receiver, | |
| Class<?>... targets) { | |
| return new Object[]{name, receiver, null, ImmutableList.copyOf(targets)}; | |
| } | |
| @Parameters(name = "{1}.{0} -> {2}") | |
| public static List<Object[]> getData() { | |
| return ImmutableList.copyOf( | |
| new Object[][] { | |
| singleTarget("singleTargetAtTop", AbstractTopClass.class), | |
| singleTargetWithAbstracts( | |
| "singleShadowingOverride", AbstractTopClass.class, AbstractSubClass.class), | |
| manyTargets( | |
| "abstractTargetAtTop", | |
| AbstractTopClass.class, | |
| SubSubClassOne.class, | |
| SubSubClassTwo.class), | |
| singleTargetWithAbstracts( | |
| "overridenInAbstractClassOnly", AbstractTopClass.class, AbstractTopClass.class), | |
| onlyUnreachableTargets("overridenInAbstractClassOnly", SubSubClassThree.class), | |
| manyTargets( | |
| "overriddenInTwoSubTypes", | |
| AbstractTopClass.class, | |
| SubSubClassOne.class, | |
| SubSubClassTwo.class), | |
| manyTargets( | |
| "definedInTwoSubTypes", | |
| AbstractTopClass.class, | |
| SubSubClassOne.class, | |
| SubSubClassTwo.class), | |
| onlyUnreachableTargets("staticMethod", AbstractTopClass.class), | |
| manyTargets("overriddenInTwoSubTypes", OtherAbstractTopClass.class), | |
| manyTargets("abstractOverriddenInTwoSubTypes", OtherAbstractTopClass.class), | |
| manyTargets("overridesOnDifferentLevels", OtherAbstractTopClass.class), | |
| singleTarget("defaultMethod", AbstractTopClass.class, InterfaceWithDefault.class), | |
| manyTargets( | |
| "overriddenDefault", | |
| AbstractTopClass.class, | |
| InterfaceWithDefault.class, | |
| SubSubClassTwo.class), | |
| singleTarget("overriddenDefault", SubSubClassTwo.class), | |
| singleTarget("overriddenByIrrelevantInterface", AbstractTopClass.class), | |
| singleTarget( | |
| "overriddenByIrrelevantInterface", SubSubClassOne.class, AbstractTopClass.class), | |
| singleTarget( | |
| "overriddenInOtherInterface", AbstractTopClass.class, InterfaceWithDefault.class), | |
| manyTargets("abstractMethod", ThirdAbstractTopClass.class), | |
| noVirtualSingleTarget("instanceMethod", ThirdAbstractTopClass.class), | |
| noVirtualSingleTarget("otherInstanceMethod", ThirdAbstractTopClass.class), | |
| }); | |
| } | |
| private static DexType toType(Class clazz, AppInfo appInfo) { | |
| return buildType(clazz, appInfo.dexItemFactory()); | |
| } | |
| private final String methodName; | |
| private final Class invokeReceiver; | |
| private final Class singleTargetHolderOrNull; | |
| private final List<Class> virtualTargetHolders; | |
| @Test | |
| public void lookupSingleTarget() { | |
| DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory()); | |
| Assert.assertNotNull( | |
| appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget()); | |
| DexEncodedMethod singleVirtualTarget = | |
| appInfo.lookupSingleVirtualTarget(method, method.holder, false); | |
| if (singleTargetHolderOrNull == null) { | |
| Assert.assertNull(singleVirtualTarget); | |
| } else { | |
| Assert.assertNotNull(singleVirtualTarget); | |
| Assert.assertEquals(toType(singleTargetHolderOrNull, appInfo), singleVirtualTarget.holder()); | |
| } | |
| } | |
| @Test | |
| public void lookupVirtualTargets() throws IOException { | |
| DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory()); | |
| Assert.assertNotNull( | |
| appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget()); | |
| ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); | |
| if (resolutionResult.isVirtualTarget()) { | |
| LookupResult lookupResult = | |
| resolutionResult.lookupVirtualDispatchTargets( | |
| appView.definitionForProgramType(buildType(Main.class, appView.dexItemFactory())), | |
| appInfo); | |
| assertTrue(lookupResult.isLookupResultSuccess()); | |
| assertFalse(lookupResult.asLookupResultSuccess().hasLambdaTargets()); | |
| Set<DexType> targetHolders = new HashSet<>(); | |
| lookupResult | |
| .asLookupResultSuccess() | |
| .forEach( | |
| methodTarget -> targetHolders.add(methodTarget.getHolder().type), | |
| lambdaTarget -> { | |
| assert false; | |
| }); | |
| Assert.assertEquals(virtualTargetHolders.size(), targetHolders.size()); | |
| assertTrue( | |
| virtualTargetHolders.stream() | |
| .map(t -> toType(t, appInfo)) | |
| .allMatch(targetHolders::contains)); | |
| } else { | |
| assertTrue(virtualTargetHolders.isEmpty()); | |
| } | |
| } | |
| } |