blob: f91f1bdf4980d3142191f0bdb04d39834bbbc456 [file] [log] [blame]
// 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());
}
}
}