Avoid adding static / private interface methods to the root set.
Bug: 121240523
Change-Id: I336165d90ed0f29399ac686203c560f79c7e639a
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 64bdee0..8ebc02f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -496,7 +496,6 @@
// At this point all code has been mapped according to the graph lens. We cannot remove the
// graph lens entirely, though, since it is needed for mapping all field and method signatures
// back to the original program.
- GraphLense finalLense = appView.graphLense();
timing.begin("AppliedGraphLens construction");
appView.setGraphLense(new AppliedGraphLens(appView, application.classes()));
timing.end();
@@ -647,8 +646,7 @@
// Validity checks.
assert application.classes().stream().allMatch(DexClass::isValid);
- // Use the final lense while checking the validity of the final app against the root set.
- assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), finalLense, options);
+ assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo());
assert appView
.graphLense()
.verifyMappingToOriginalProgram(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 0b5e17e..7065297 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -180,7 +180,7 @@
this.stringConcatRewriter = new StringConcatRewriter(appInfo);
this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
this.interfaceMethodRewriter =
- (options.enableDesugaring && enableInterfaceMethodDesugaring())
+ options.isInterfaceMethodDesugaringEnabled()
? new InterfaceMethodRewriter(this, options) : null;
this.twrCloseResourceRewriter =
(options.enableDesugaring && enableTwrCloseResourceDesugaring())
@@ -285,16 +285,6 @@
this(appView.appInfo(), options, timing, printer, appView, mainDexClasses, rootSet);
}
- private boolean enableInterfaceMethodDesugaring() {
- switch (options.interfaceMethodDesugaring) {
- case Off:
- return false;
- case Auto:
- return !options.canUseDefaultAndStaticInterfaceMethods();
- }
- throw new Unreachable();
- }
-
private boolean enableTwrCloseResourceDesugaring() {
return enableTryWithResourcesDesugaring() && !options.canUseTwrCloseResourceMethod();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 86b29a8..f3ac326 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -273,6 +273,19 @@
return false;
}
+ public boolean isSpecific() {
+ switch (getRuleType()) {
+ case ALL:
+ // fall through
+ case ALL_FIELDS:
+ // fall through
+ case ALL_METHODS:
+ return false;
+ default:
+ return Iterables.size(getWildcards()) == 0;
+ }
+ }
+
Iterable<ProguardWildcard> getWildcards() {
return Iterables.concat(
ProguardTypeMatcher.getWildcardsOrEmpty(annotation),
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 43c39a6..9e6be06 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -30,7 +30,6 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
@@ -896,11 +895,30 @@
ProguardMemberRule rule,
DexDefinition precondition) {
if (context instanceof ProguardKeepRule) {
- if (item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isSynthetic()) {
- // Don't keep synthetic methods (see b/120971047 for additional details).
- // TODO(b/122819537): need to distinguish lambda desugared synthetic methods v.s. kotlinc
- // synthetic methods?
- return;
+ if (item.isDexEncodedMethod()) {
+ DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
+ if (encodedMethod.isSyntheticMethod()) {
+ // Don't keep synthetic methods (see b/120971047 for additional details).
+ // TODO(b/122819537): need to distinguish lambda desugared synthetic methods v.s. kotlinc
+ // synthetic methods?
+ return;
+ }
+ // If desugaring is enabled, private and static interface methods will be moved to a
+ // companion class. So we don't need to add them to the root set in the beginning.
+ if (options.isInterfaceMethodDesugaringEnabled()
+ && encodedMethod.hasCode()
+ && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
+ DexClass holder = appView.appInfo().definitionFor(encodedMethod.method.getHolder());
+ if (holder != null && holder.isInterface()) {
+ if (rule.isSpecific()) {
+ options.reporter.warning(
+ new StringDiagnostic(
+ "The rule `" + rule + "` is ignored because the targeting interface method `"
+ + encodedMethod.method.toSourceString() + "` will be desugared."));
+ }
+ return;
+ }
+ }
}
ProguardKeepRule keepRule = (ProguardKeepRule) context;
@@ -1041,8 +1059,8 @@
this.keepUnusedArguments = keepUnusedArguments;
this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
- this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
- this.assumedValues = Collections.unmodifiableMap(assumedValues);
+ this.noSideEffects = noSideEffects;
+ this.assumedValues = assumedValues;
this.dependentNoShrinking = dependentNoShrinking;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
@@ -1064,8 +1082,10 @@
lense.rewriteMutableMethodsConservatively(previous.keepUnusedArguments);
this.neverClassInline = lense.rewriteMutableTypesConservatively(previous.neverClassInline);
this.neverMerge = lense.rewriteTypesConservatively(previous.neverMerge);
- this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
- this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
+ this.noSideEffects =
+ rewriteMutableReferenceKeys(previous.noSideEffects, lense::lookupReference);
+ this.assumedValues =
+ rewriteMutableReferenceKeys(previous.assumedValues, lense::lookupReference);
this.dependentNoShrinking =
rewriteDependentReferenceKeys(previous.dependentNoShrinking, lense::lookupReference);
this.identifierNameStrings =
@@ -1109,6 +1129,37 @@
dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
}
+ public void copy(DexReference original, DexReference rewritten) {
+ if (noShrinking.containsKey(original)) {
+ noShrinking.put(rewritten, noShrinking.get(original));
+ }
+ if (noOptimization.contains(original)) {
+ noOptimization.add(rewritten);
+ }
+ if (noObfuscation.contains(original)) {
+ noObfuscation.add(rewritten);
+ }
+ if (noSideEffects.containsKey(original)) {
+ noSideEffects.put(rewritten, noSideEffects.get(original));
+ }
+ if (assumedValues.containsKey(original)) {
+ assumedValues.put(rewritten, assumedValues.get(original));
+ }
+ }
+
+ public void prune(DexReference reference) {
+ noShrinking.remove(reference);
+ noOptimization.remove(reference);
+ noObfuscation.remove(reference);
+ noSideEffects.remove(reference);
+ assumedValues.remove(reference);
+ }
+
+ public void move(DexReference original, DexReference rewritten) {
+ copy(original, rewritten);
+ prune(original);
+ }
+
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
for (DexReference reference : noShrinking.keySet()) {
if (reference.isDexField()) {
@@ -1173,40 +1224,7 @@
return false;
}
- // TODO(b/121240523): Ideally, these default interface methods should be delegated.
- private boolean isDesugaredMethod(
- AppInfo appInfo, GraphLense lense, DexClass clazz, DexMethod method) {
- if (clazz == null) {
- clazz = appInfo.definitionFor(method.getHolder());
- }
- Set<DexMethod> lookupMethods = lense.lookupMethodInAllContexts(method);
- if (lookupMethods.size() == 1) {
- for (DexMethod lookupMethod : lookupMethods) {
- if (lookupMethod == method) {
- continue;
- }
- DexEncodedMethod encodedMethod = appInfo.definitionFor(lookupMethod);
- if (clazz != null
- && clazz.isInterface()
- && encodedMethod != null
- && encodedMethod.hasCode()) {
- return true;
- }
- }
- }
- return false;
- }
-
- public boolean verifyKeptItemsAreKept(
- DexApplication application,
- AppInfo appInfo,
- GraphLense lense,
- InternalOptions options) {
- boolean isInterfaceMethodDesugaringEnabled =
- options.enableDesugaring
- && options.interfaceMethodDesugaring == OffOrAuto.Auto
- && !options.canUseDefaultAndStaticInterfaceMethods();
-
+ public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
Set<DexReference> pinnedItems =
appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
@@ -1214,10 +1232,7 @@
Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
for (DexReference reference : noShrinking.keySet()) {
// Check that `pinnedItems` is a super set of the root set.
- assert pinnedItems == null || pinnedItems.contains(reference)
- || (reference.isDexMethod()
- && isInterfaceMethodDesugaringEnabled
- && isDesugaredMethod(appInfo, lense, null, reference.asDexMethod()));
+ assert pinnedItems == null || pinnedItems.contains(reference);
if (reference.isDexType()) {
DexType type = reference.asDexType();
requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
@@ -1251,10 +1266,6 @@
assert fields.contains(requiredField);
} else if (requiredReference.isDexMethod()) {
DexMethod requiredMethod = requiredReference.asDexMethod();
- if (isInterfaceMethodDesugaringEnabled
- && isDesugaredMethod(appInfo, lense, clazz, requiredMethod)) {
- continue;
- }
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
methods =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 7f4fbe1..a35100e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -586,6 +586,16 @@
return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
}
+ public boolean isInterfaceMethodDesugaringEnabled() {
+ // This condition is to filter out tests that never set program consumer.
+ if (!hasConsumer()) {
+ return false;
+ }
+ return enableDesugaring
+ && interfaceMethodDesugaring == OffOrAuto.Auto
+ && !canUseDefaultAndStaticInterfaceMethods();
+ }
+
public boolean canUseMultidex() {
assert isGeneratingDex();
return intermediate || hasMinApi(AndroidApiLevel.L);
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index a9c02ff..ecb0a92 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -55,6 +55,11 @@
return state.getDiagnosticsMessages();
}
+ public R8TestCompileResult inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) {
+ consumer.accept(state.getDiagnosticsMessages());
+ return self();
+ }
+
@Override
public CodeInspector inspector() throws IOException, ExecutionException {
return new CodeInspector(app, proguardMap);
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
new file mode 100644
index 0000000..ab37cb0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, 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.shaking.desugar;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+@NeverMerge
+interface I {
+ static void foo() {
+ System.out.println("static::foo");
+ }
+
+ default void bar() {
+ foo();
+ System.out.println("default::bar");
+ }
+}
+
+class C implements I {
+}
+
+class KeepRuleWarningTestRunner {
+ public static void main(String[] args) {
+ C obj = new C();
+ obj.bar();
+ }
+}
+
+public class KeepRuleWarningTest extends TestBase {
+ private static final Class<?> MAIN = KeepRuleWarningTestRunner.class;
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("static::foo", "default::bar");
+
+ @Test
+ public void test_allMethods() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { <methods>; }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_asterisk() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { *(); }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_stillNotSpecific() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { *** f*(); }")
+ .compile()
+ .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void test_specific() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { static void foo(); }")
+ .compile()
+ .inspectDiagnosticMessages(m -> {
+ m.assertWarningsCount(1)
+ .assertWarningMessageThatMatches(containsString("static void foo()"))
+ .assertWarningMessageThatMatches(containsString("is ignored"))
+ .assertWarningMessageThatMatches(containsString("will be desugared"));
+ })
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
new file mode 100644
index 0000000..5038729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2019, 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.shaking.desugar.interfacemethods;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+import org.junit.Test;
+
+@NeverMerge
+interface I {
+ default void m() {
+ System.out.println("I::m");
+ }
+}
+
+class C implements I {
+}
+
+class BridgeInliningTestRunner {
+ public static void main(String[] args) throws Exception {
+ C obj = new C();
+ for (Method m : obj.getClass().getDeclaredMethods()) {
+ m.invoke(obj, null);
+ }
+ }
+}
+
+public class BridgeInliningTest extends TestBase {
+ private static final Class<?> MAIN = BridgeInliningTestRunner.class;
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("I::m");
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(I.class, C.class, MAIN)
+ .setMinApi(AndroidApiLevel.L)
+ .enableMergeAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep interface **.I { m(); }")
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector codeInspector) {
+ ClassSubject c = codeInspector.clazz(C.class);
+ assertThat(c, isPresent());
+ MethodSubject m = c.uniqueMethodWithName("m");
+ assertThat(m, isPresent());
+ assertTrue(m.getMethod().hasCode());
+ // TODO(b/123778921): why are I and C not merged without merge annotations?
+ //assertTrue(
+ // m.iterateInstructions(i -> i.isConstString("I::m", JumboStringMode.ALLOW)).hasNext());
+
+ // TODO(b/123778921): No companion class is in the output.
+ //codeInspector.forAllClasses(classSubject -> {
+ // assertFalse(classSubject.getOriginalDescriptor().contains("$-CC"));
+ //});
+ }
+
+}