| // 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 com.android.tools.r8.AsmTestBase; |
| import com.android.tools.r8.dex.ApplicationReader; |
| import com.android.tools.r8.graph.AppInfoWithSubtyping; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.GraphLense; |
| 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.IrrelevantInterfaceWithDefault; |
| 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.Enqueuer; |
| import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.ProguardClassFilter; |
| import com.android.tools.r8.shaking.ProguardClassNameList; |
| import com.android.tools.r8.shaking.ProguardConfigurationRule; |
| import com.android.tools.r8.shaking.ProguardKeepRule; |
| import com.android.tools.r8.shaking.ProguardKeepRule.Builder; |
| import com.android.tools.r8.shaking.ProguardKeepRuleType; |
| import com.android.tools.r8.shaking.ProguardTypeMatcher; |
| import com.android.tools.r8.shaking.RootSetBuilder; |
| import com.android.tools.r8.shaking.RootSetBuilder.RootSet; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Timing; |
| import com.google.common.collect.ImmutableList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.stream.Collectors; |
| 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 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> allTargetHolders) { |
| this.methodName = methodName; |
| this.invokeReceiver = invokeReceiver; |
| this.singleTargetHolderOrNull = singleTargetHolderOrNull; |
| this.allTargetHolders = allTargetHolders; |
| } |
| |
| @BeforeClass |
| public static void computeAppInfo() throws Exception { |
| // Run the tree shaker to compute an instance of AppInfoWithLiveness. |
| Timing timing = new Timing(SingleTargetLookupTest.class.getCanonicalName()); |
| InternalOptions options = new InternalOptions(); |
| AndroidApp app = readClassesAndAsmDump(CLASSES, ASM_CLASSES); |
| DexApplication application = new ApplicationReader(app, options, timing).read().toDirect(); |
| AppView<? extends AppInfoWithSubtyping> appView = |
| new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense()); |
| |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| RootSet rootSet = |
| new RootSetBuilder( |
| appView, |
| application, |
| buildKeepRuleForClass(Main.class, application.dexItemFactory), |
| options) |
| .run(executor); |
| appInfo = |
| new Enqueuer(appView, options, null) |
| .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing); |
| // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified |
| // due to liveness. |
| } |
| |
| private static List<ProguardConfigurationRule> buildKeepRuleForClass(Class clazz, |
| DexItemFactory factory) { |
| Builder keepRuleBuilder = ProguardKeepRule.builder(); |
| keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName()); |
| keepRuleBuilder.setType(ProguardKeepRuleType.KEEP); |
| keepRuleBuilder.setClassNames( |
| ProguardClassNameList.singletonList(ProguardTypeMatcher.create(factory |
| .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()))))); |
| return Collections.singletonList(keepRuleBuilder.build()); |
| } |
| |
| 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, AbstractTopClass.class), |
| manyTargets("abstractTargetAtTop", AbstractTopClass.class, AbstractTopClass.class, |
| SubSubClassOne.class, SubSubClassTwo.class), |
| singleTargetWithAbstracts("overridenInAbstractClassOnly", AbstractTopClass.class, |
| AbstractTopClass.class, SubSubClassThree.class), |
| onlyUnreachableTargets("overridenInAbstractClassOnly", SubSubClassThree.class, |
| SubSubClassThree.class), |
| manyTargets("overriddenInTwoSubTypes", AbstractTopClass.class, AbstractTopClass.class, |
| SubSubClassOne.class, SubSubClassTwo.class), |
| manyTargets("definedInTwoSubTypes", AbstractTopClass.class, AbstractTopClass.class, |
| SubSubClassOne.class, SubSubClassTwo.class), |
| onlyUnreachableTargets("staticMethod", AbstractTopClass.class, AbstractTopClass.class), |
| manyTargets("overriddenInTwoSubTypes", OtherAbstractTopClass.class, |
| OtherAbstractTopClass.class, OtherSubSubClassOne.class, OtherSubSubClassTwo.class), |
| manyTargets("abstractOverriddenInTwoSubTypes", OtherAbstractTopClass.class, |
| OtherAbstractTopClass.class, OtherSubSubClassOne.class, OtherSubSubClassTwo.class), |
| manyTargets("overridesOnDifferentLevels", OtherAbstractTopClass.class, |
| OtherAbstractTopClass.class, OtherSubSubClassOne.class, OtherAbstractSubClassTwo.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), |
| manyTargets("overriddenInOtherInterface", AbstractTopClass.class, |
| InterfaceWithDefault.class, |
| IrrelevantInterfaceWithDefault.class), |
| manyTargets("overriddenInOtherInterface", SubSubClassOne.class, |
| InterfaceWithDefault.class, |
| IrrelevantInterfaceWithDefault.class), |
| manyTargets("abstractMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class, |
| ThirdSubClassOne.class), |
| singleTarget("instanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class), |
| singleTarget( |
| "otherInstanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class) |
| }); |
| } |
| |
| private static DexMethod buildMethod(Class clazz, String name) { |
| return appInfo.dexItemFactory.createMethod( |
| toType(clazz), |
| appInfo.dexItemFactory.createProto(appInfo.dexItemFactory.voidType), |
| name |
| ); |
| } |
| |
| private static DexType toType(Class clazz) { |
| return appInfo.dexItemFactory |
| .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName())); |
| } |
| |
| private final String methodName; |
| private final Class invokeReceiver; |
| private final Class singleTargetHolderOrNull; |
| private final List<Class> allTargetHolders; |
| |
| @Test |
| public void lookupSingleTarget() { |
| DexMethod method = buildMethod(invokeReceiver, methodName); |
| Assert.assertNotNull(appInfo.resolveMethod(toType(invokeReceiver), method).asResultOfResolve()); |
| DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method); |
| if (singleTargetHolderOrNull == null) { |
| Assert.assertNull(singleVirtualTarget); |
| } else { |
| Assert.assertNotNull(singleVirtualTarget); |
| Assert.assertEquals(toType(singleTargetHolderOrNull), singleVirtualTarget.method.holder); |
| } |
| } |
| |
| @Test |
| public void lookupVirtualTargets() { |
| DexMethod method = buildMethod(invokeReceiver, methodName); |
| Assert.assertNotNull(appInfo.resolveMethod(toType(invokeReceiver), method).asResultOfResolve()); |
| Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(method); |
| Set<DexType> targetHolders = targets.stream().map(m -> m.method.holder) |
| .collect(Collectors.toSet()); |
| Assert.assertEquals(allTargetHolders.size(), targetHolders.size()); |
| Assert.assertTrue(allTargetHolders.stream().map(SingleTargetLookupTest::toType) |
| .allMatch(targetHolders::contains)); |
| } |
| } |