blob: 7f1ec89588531fa76fecd1dc827268ff53867a66 [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 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.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.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(), options);
appView.setAppServices(AppServices.builder(appView).build());
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));
}
}