| // 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.assertTrue; |
| |
| import com.android.tools.r8.AsmTestBase; |
| import com.android.tools.r8.dex.ApplicationReader; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.AppInfoWithSubtyping; |
| import com.android.tools.r8.graph.AppServices; |
| 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.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.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.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.EnqueuerFactory; |
| 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; |
| } |
| |
| public static AppInfoWithLiveness createAppInfoWithLiveness(AndroidApp app, Class<?> mainClass) |
| throws Exception { |
| // Run the tree shaker to compute an instance of AppInfoWithLiveness. |
| Timing timing = new Timing(); |
| InternalOptions options = new InternalOptions(); |
| DexApplication application = new ApplicationReader(app, options, timing).read().toDirect(); |
| AppView<? extends AppInfoWithSubtyping> appView = |
| AppView.createForR8(new AppInfoWithSubtyping(application), options); |
| appView.setAppServices(AppServices.builder(appView).build()); |
| |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| RootSet rootSet = |
| new RootSetBuilder( |
| appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory)) |
| .run(executor); |
| return EnqueuerFactory.createForInitialTreeShaking(appView) |
| .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. |
| } |
| |
| @BeforeClass |
| public static void computeAppInfo() throws Exception { |
| appInfo = createAppInfoWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class); |
| } |
| |
| 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.getTypeName()))))); |
| 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), |
| 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), |
| }); |
| } |
| |
| public static DexMethod buildMethod(Class clazz, String name, AppInfo appInfo) { |
| return appInfo |
| .dexItemFactory() |
| .createMethod( |
| toType(clazz, appInfo), |
| appInfo.dexItemFactory().createProto(appInfo.dexItemFactory().voidType), |
| name); |
| } |
| |
| public static DexType toType(Class clazz, AppInfo appInfo) { |
| return appInfo |
| .dexItemFactory() |
| .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())); |
| } |
| |
| 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, appInfo); |
| Assert.assertNotNull( |
| appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve()); |
| DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder); |
| if (singleTargetHolderOrNull == null) { |
| Assert.assertNull(singleVirtualTarget); |
| } else { |
| Assert.assertNotNull(singleVirtualTarget); |
| Assert.assertEquals( |
| toType(singleTargetHolderOrNull, appInfo), singleVirtualTarget.method.holder); |
| } |
| } |
| |
| @Test |
| public void lookupVirtualTargets() { |
| DexMethod method = buildMethod(invokeReceiver, methodName, appInfo); |
| Assert.assertNotNull( |
| appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve()); |
| ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); |
| if (resolutionResult.isValidVirtualTarget(appInfo.app().options)) { |
| Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo); |
| Set<DexType> targetHolders = |
| targets.stream().map(m -> m.method.holder).collect(Collectors.toSet()); |
| Assert.assertEquals(allTargetHolders.size(), targetHolders.size()); |
| assertTrue( |
| allTargetHolders.stream().map(t -> toType(t, appInfo)).allMatch(targetHolders::contains)); |
| } else { |
| assertTrue(allTargetHolders.isEmpty()); |
| } |
| } |
| } |