blob: 745ff4b5fdfc12b0f6f5df542f42c4e95c2a55ad [file] [log] [blame]
// Copyright (c) 2020, 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.virtualtargets;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
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 KeptTargetsIncompleteLookupTest extends TestBase {
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public KeptTargetsIncompleteLookupTest(TestParameters parameters) {
// Empty to satisfy construction of none-runtime.
}
private LookupResultSuccess testLookup(Class<?> methodToBeKept) throws Exception {
return testLookup(B.class, methodToBeKept, methodToBeKept, A.class, C.class);
}
private LookupResultSuccess testLookup(
Class<?> initial,
Class<?> methodToBeKept,
Class<?> classToBeKept,
Class<?>... expectedMethodHolders)
throws Exception {
return testLookup(
initial,
methodToBeKept,
classToBeKept,
Arrays.asList(expectedMethodHolders),
Arrays.asList(I.class, A.class, B.class, C.class, Main.class),
Main.class);
}
private LookupResultSuccess testLookup(
Class<?> initial,
Class<?> methodToBeKept,
Class<?> classToBeKept,
Collection<Class<?>> expectedMethodHolders,
Collection<Class<?>> classes,
Class<?> main)
throws Exception {
AppView<AppInfoWithLiveness> appView =
computeAppViewWithLiveness(
buildClasses(classes).build(),
factory -> {
List<ProguardConfigurationRule> rules = new ArrayList<>();
rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
rules.addAll(buildKeepRuleForClassAndMethods(main, factory));
return rules;
});
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
DexProgramClass context =
appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
assertTrue(lookupResult.isLookupResultSuccess());
LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
Set<String> targets = new HashSet<>();
lookupResult.forEach(
target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
Set<String> expected =
expectedMethodHolders.stream()
.map(c -> c.getTypeName() + ".foo")
.collect(Collectors.toSet());
assertEquals(expected, targets);
return lookupResultSuccess;
}
@Test
public void testCompleteLookupResultWhenKeepingUnrelated() throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// B extends A { } <-- initial
//
// C extends B {
// foo()
// }
assertTrue(
testLookup(B.class, Unrelated.class, Unrelated.class, A.class, C.class).isComplete());
}
@Test
public void testKeptResolvedAndNoKeepInSubtreeFromInitial() throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- kept, resolved
// }
//
// B extends A { } <-- initial
//
// C extends B {
// foo()
// }
assertTrue(testLookup(A.class).isIncomplete());
}
@Test
public void testIncompleteLookupResultWhenKeepingStaticReceiver() throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// B extends A { } <-- initial, kept
//
// C extends B {
// foo()
// }
assertTrue(testLookup(B.class).isIncomplete());
}
@Test
public void testIncompleteLookupResultWhenKeepingSubTypeMethod() throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// B extends A { } <-- initial
//
// C extends B {
// foo() <-- kept
// }
assertTrue(testLookup(C.class).isIncomplete());
}
@Test
public void testIncompleteLookupResultWhenKeepingMethodOnParentToResolveAndKeepClass()
throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- kept
// }
//
// B extends A { }
//
// C extends B { <-- initial, resolved, kept
// foo()
// }
assertTrue(testLookup(C.class, A.class, C.class, C.class).isIncomplete());
}
@Test
public void testCompleteLookupResultWhenKeepingMethodOnParentToResolveAndNotKeepClass()
throws Exception {
// I {
// foo()
// }
//
// A implements I {
// foo() <-- kept
// }
//
// B extends A { }
//
// C extends B { <-- initial, resolved
// foo()
// }
assertTrue(testLookup(C.class, A.class, Unrelated.class, C.class).isComplete());
}
@Test
public void testLibraryWithNoOverride() throws Exception {
// ----- Library -----
// I {
// foo()
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// ----- Program -----
// B extends A { } <-- initial
AppView<AppInfoWithClassHierarchy> appView =
computeAppViewWithSubtyping(
buildClasses(Collections.singletonList(B.class), Arrays.asList(A.class, I.class))
.build());
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
DexType typeA = buildType(A.class, appInfo.dexItemFactory());
DexType typeB = buildType(B.class, appInfo.dexItemFactory());
DexProgramClass classB = appInfo.definitionForProgramType(typeB);
LookupResult lookupResult =
resolutionResult.lookupVirtualDispatchTargets(
classB,
appInfo,
(type, subTypeConsumer, callSiteConsumer) -> {
if (type == typeB) {
subTypeConsumer.accept(classB);
}
},
reference -> false);
assertTrue(lookupResult.isLookupResultSuccess());
LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
Set<String> targets = new HashSet<>();
lookupResult.forEach(
target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
Set<String> expected = ImmutableSet.of(A.class.getTypeName() + ".foo");
assertEquals(expected, targets);
assertTrue(lookupResultSuccess.isComplete());
}
@Test
public void testPrivateKeep() throws Exception {
// Unrelated { <-- kept
// private foo() <-- kept
// }
AppView<AppInfoWithLiveness> appView =
computeAppViewWithLiveness(buildClasses(Unrelated.class).build(), Unrelated.class);
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Unrelated.class, "foo", appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
DexProgramClass context =
appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
assertTrue(lookupResult.isLookupResultSuccess());
LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
Set<String> targets = new HashSet<>();
lookupResult.forEach(
target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
Set<String> expected = ImmutableSet.of(Unrelated.class.getTypeName() + ".foo");
assertEquals(expected, targets);
assertTrue(lookupResultSuccess.isIncomplete());
}
@Test
public void testInterfaceKept() throws Exception {
// I {
// foo() <-- kept
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// B extends A { } <-- initial, kept
//
// C extends B {
// foo()
// }
assertTrue(testLookup(B.class, I.class, B.class, A.class, C.class).isIncomplete());
}
@Test
public void testInterfaceKeptWithoutKeepInLookup() throws Exception {
// I {
// foo() <-- kept
// }
//
// A implements I {
// foo() <-- resolved
// }
//
// B extends A { } <-- initial
//
// C extends B {
// foo()
// }
assertTrue(testLookup(B.class, I.class, Unrelated.class, A.class, C.class).isComplete());
}
@Test
public void testInterfaceKeptAndImplementedInSupType() throws Exception {
// X {
// foo() <-- resolved
// }
//
// I {
// foo() <-- kept
// }
//
// Y extends X implements I { } <-- initial
//
// Z extends Y { } <-- kept
assertTrue(
testLookup(
Y.class,
I.class,
Z.class,
Collections.singleton(X.class),
Arrays.asList(X.class, I.class, Y.class, Z.class),
MainXYZ.class)
.isIncomplete());
}
// TODO(b/148769279): We need to look at the call site to see if it overrides
// a method that is kept.
public static class Unrelated {
private void foo() {
System.out.println("Unrelated.foo");
}
}
public interface I {
void foo();
}
public static class A implements I {
@Override
public void foo() {
System.out.println("A.foo");
}
}
public static class B extends A {}
public static class C extends B {
@Override
public void foo() {
System.out.println("C.foo");
}
}
public static class Main {
public static void main(String[] args) {
// This is necessary for considering the classes as instantiated.
new Unrelated();
new A();
new B();
new C();
}
}
public static class X {
public void foo() {
System.out.println("X.foo");
}
}
public static class Y extends X implements I {}
public static class Z extends Y {}
public static class MainXYZ {
public static void main(String[] args) {
// This is necessary for considering the classes as instantiated.
new X();
new Y();
new Z();
}
}
}