Use original static modifier when evaluating -if rules
Bug: 117403482, 122867080, 122875545, 122867087, 120266482
Change-Id: Ieff6fd207cdf187b4787530ee66af7d3a97ec94c
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 09efa690..63c1cde 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -194,6 +194,10 @@
promote(Constants.ACC_PUBLIC);
}
+ public void promoteToStatic() {
+ promote(Constants.ACC_STATIC);
+ }
+
private boolean wasSet(int flag) {
return (originalFlags & flag) != 0;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 1081eff..44294db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -685,7 +685,8 @@
public DexEncodedMethod toStaticMethodWithoutThis() {
checkIfObsolete();
assert !accessFlags.isStatic();
- Builder builder = builder(this).setStatic().unsetOptimizationInfo().withoutThisParameter();
+ Builder builder =
+ builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
setObsolete();
return builder.build();
}
@@ -1255,8 +1256,8 @@
this.method = method;
}
- public Builder setStatic() {
- this.accessFlags.setStatic();
+ public Builder promoteToStatic() {
+ this.accessFlags.promoteToStatic();
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 6ba9b6a..4f335a6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -85,7 +85,7 @@
MethodAccessFlags newFlags = virtual.accessFlags.copy();
newFlags.unsetBridge();
- newFlags.setStatic();
+ newFlags.promoteToStatic();
DexCode dexCode = code.asDexCode();
// We cannot name the parameter "this" because the debugger may omit it due to the method
// actually being static. Instead we prepend it with a special character.
@@ -124,8 +124,7 @@
MethodAccessFlags originalFlags = direct.accessFlags;
MethodAccessFlags newFlags = originalFlags.copy();
if (originalFlags.isPrivate()) {
- newFlags.unsetPrivate();
- newFlags.setPublic();
+ newFlags.promoteToPublic();
}
DexMethod oldMethod = direct.method;
@@ -143,7 +142,7 @@
assert !rewriter.factory.isClassConstructor(oldMethod)
: "Unexpected private constructor " + direct.toSourceString()
+ " in " + iface.origin;
- newFlags.setStatic();
+ newFlags.promoteToStatic();
DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
new file mode 100644
index 0000000..a12620d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
@@ -0,0 +1,113 @@
+// 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.ifrule.classstaticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+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.FoundMethodSubject;
+import java.util.List;
+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 IfRuleWithClassStaticizerTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public IfRuleWithClassStaticizerTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("In method()");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(IfRuleWithClassStaticizerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + StaticizerCandidate.Companion.class.getTypeName() + " {",
+ " public !static void method();",
+ "}",
+ "-keep class " + Unused.class.getTypeName())
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classSubject = inspector.clazz(StaticizerCandidate.class);
+ assertThat(classSubject, isPresent());
+
+ if (backend == Backend.CF) {
+ // The class staticizer is not enabled for CF.
+ assertThat(inspector.clazz(Unused.class), isPresent());
+ } else {
+ assert backend == Backend.DEX;
+
+ // There should be a static method on StaticizerCandidate after staticizing.
+ List<FoundMethodSubject> staticMethods =
+ classSubject.allMethods().stream()
+ .filter(method -> method.isStatic() && !method.isClassInitializer())
+ .collect(Collectors.toList());
+ assertEquals(1, staticMethods.size());
+ assertEquals(
+ "void " + StaticizerCandidate.Companion.class.getTypeName() + ".method()",
+ staticMethods.get(0).getOriginalSignature().toString());
+
+ // The Companion class should not be present after staticizing.
+ assertThat(inspector.clazz(StaticizerCandidate.Companion.class), not(isPresent()));
+
+ // TODO(b/122867080): The Unused class should be present due to the -if rule.
+ assertThat(inspector.clazz(Unused.class), not(isPresent()));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ StaticizerCandidate.companion.method();
+ }
+ }
+
+ @NeverClassInline
+ static class StaticizerCandidate {
+
+ static final Companion companion = new Companion();
+
+ @NeverClassInline
+ static class Companion {
+
+ @NeverInline
+ public void method() {
+ System.out.println("In method()");
+ }
+ }
+ }
+
+ static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
new file mode 100644
index 0000000..d86bc68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -0,0 +1,105 @@
+// 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.ifrule.interfacemethoddesugaring;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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 org.junit.Test;
+
+public class IfRuleWithInterfaceMethodDesugaringTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines("In Interface.staticMethod()", "In Interface.virtualMethod()");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + Interface.class.getTypeName() + " {",
+ " !public static void staticMethod();",
+ "}",
+ "-keep class " + Unused1.class.getTypeName(),
+ "-if class " + Interface.class.getTypeName() + " {",
+ " !public !static void virtualMethod();",
+ "}",
+ "-keep class " + Unused2.class.getTypeName())
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(AndroidApiLevel.M)
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classSubject =
+ inspector.clazz(Interface.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+ assertThat(classSubject, isPresent());
+ assertEquals(2, classSubject.allMethods().size());
+
+ MethodSubject staticMethodSubject = classSubject.uniqueMethodWithName("staticMethod");
+ assertThat(staticMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+ // TODO(b/122867087): Should not be necessary to use `DEFAULT_METHOD_PREFIX`.
+ MethodSubject virtualMethodSubject =
+ classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "virtualMethod");
+ assertThat(virtualMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+ // TODO(b/122875545): The Unused class should be present due to the -if rule.
+ assertThat(inspector.clazz(Unused1.class), not(isPresent()));
+ assertThat(inspector.clazz(Unused2.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Interface.staticMethod();
+ new InterfaceImpl().virtualMethod();
+ }
+ }
+
+ @NeverClassInline
+ @NeverMerge
+ interface Interface {
+
+ @NeverInline
+ static void staticMethod() {
+ System.out.println("In Interface.staticMethod()");
+ }
+
+ @NeverInline
+ default void virtualMethod() {
+ System.out.println("In Interface.virtualMethod()");
+ }
+ }
+
+ @NeverClassInline
+ static class InterfaceImpl implements Interface {}
+
+ static class Unused1 {}
+
+ static class Unused2 {}
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 8cd5cc7..11ee3af 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -102,7 +102,7 @@
public MethodSubject uniqueMethodWithName(String name) {
MethodSubject methodSubject = null;
for (FoundMethodSubject candidate : allMethods()) {
- if (candidate.getOriginalName().equals(name)) {
+ if (candidate.getOriginalName(false).equals(name)) {
assert methodSubject == null;
methodSubject = candidate;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 4ea2d93..103ffb9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -146,6 +146,28 @@
};
}
+ public static Matcher<MemberSubject> isStatic() {
+ return new TypeSafeMatcher<MemberSubject>() {
+ @Override
+ public boolean matchesSafely(final MemberSubject subject) {
+ return subject.isPresent() && subject.isStatic();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(" present");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MemberSubject subject, Description description) {
+ description
+ .appendText(type(subject) + " ")
+ .appendValue(name(subject))
+ .appendText(" was not");
+ }
+ };
+ }
+
public static Matcher<Subject> isSynthetic() {
return new TypeSafeMatcher<Subject>() {
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index a95b6e6..af1fe14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -25,8 +25,22 @@
public abstract Signature getFinalSignature();
public String getOriginalName() {
+ return getOriginalName(true);
+ }
+
+ public String getOriginalName(boolean qualified) {
Signature originalSignature = getOriginalSignature();
- return originalSignature == null ? null : originalSignature.name;
+ if (originalSignature != null) {
+ String name = originalSignature.name;
+ if (!qualified) {
+ int index = name.lastIndexOf(".");
+ if (index >= 0) {
+ return name.substring(index + 1);
+ }
+ }
+ return name;
+ }
+ return null;
}
public String getFinalName() {