Merge changes Ieff6fd20,I16a59957 * changes: Use original static modifier when evaluating -if rules Use original final modifier when evaluating -if rules
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 ebeae02..63c1cde 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -45,11 +45,12 @@ return NAMES; } - protected int flags; - protected boolean isPublicized = false; + protected int originalFlags; + protected int modifiedFlags; - protected AccessFlags(int flags) { - this.flags = flags; + protected AccessFlags(int originalFlags, int modifiedFlags) { + this.originalFlags = originalFlags; + this.modifiedFlags = modifiedFlags; } public abstract T copy(); @@ -57,10 +58,7 @@ public abstract T self(); public int materialize() { - if (isPromotedToPublic()) { - return ((flags | Constants.ACC_PUBLIC) & ~Constants.ACC_PROTECTED) & ~Constants.ACC_PRIVATE; - } - return flags; + return modifiedFlags; } public abstract int getAsCfAccessFlags(); @@ -68,21 +66,21 @@ public abstract int getAsDexAccessFlags(); public final int getOriginalCfAccessFlags() { - return flags; + return originalFlags; } @Override public boolean equals(Object object) { if (object instanceof AccessFlags) { AccessFlags other = (AccessFlags) object; - return flags == other.flags && isPublicized == other.isPublicized; + return originalFlags == other.originalFlags && modifiedFlags == other.modifiedFlags; } return false; } @Override public int hashCode() { - return (flags << 1) | (isPublicized ? 1 : 0); + return originalFlags | modifiedFlags; } public boolean isMoreVisibleThan(AccessFlags other) { @@ -109,7 +107,7 @@ } public boolean isPublic() { - return isSet(Constants.ACC_PUBLIC) || isPromotedToPublic(); + return isSet(Constants.ACC_PUBLIC); } public void setPublic() { @@ -122,7 +120,7 @@ } public boolean isPrivate() { - return isSet(Constants.ACC_PRIVATE) && !isPromotedToPublic(); + return isSet(Constants.ACC_PRIVATE); } public void setPrivate() { @@ -135,7 +133,7 @@ } public boolean isProtected() { - return isSet(Constants.ACC_PROTECTED) && !isPromotedToPublic(); + return isSet(Constants.ACC_PROTECTED); } public void setProtected() { @@ -179,35 +177,56 @@ unset(Constants.ACC_SYNTHETIC); } - public boolean isPromotedToPublic() { - return isPublicized; + public void promoteToFinal() { + promote(Constants.ACC_FINAL); } - public T setPromotedToPublic(boolean isPublicized) { - this.isPublicized = isPublicized; - return self(); + public void demoteFromFinal() { + demote(Constants.ACC_FINAL); + } + + public boolean isPromotedToPublic() { + return isPromoted(Constants.ACC_PUBLIC); } public void promoteToPublic() { - isPublicized = true; + demote(Constants.ACC_PRIVATE | Constants.ACC_PROTECTED); + promote(Constants.ACC_PUBLIC); } - public void unsetPromotedToPublic() { - isPublicized = false; + public void promoteToStatic() { + promote(Constants.ACC_STATIC); + } + + private boolean wasSet(int flag) { + return (originalFlags & flag) != 0; } protected boolean isSet(int flag) { - return (flags & flag) != 0; + return (modifiedFlags & flag) != 0; } protected void set(int flag) { - flags |= flag; + originalFlags |= flag; + modifiedFlags |= flag; } protected void unset(int flag) { - flags &= ~flag; + originalFlags &= ~flag; + modifiedFlags &= ~flag; } + protected boolean isPromoted(int flag) { + return !wasSet(flag) && isSet(flag); + } + + protected void promote(int flag) { + modifiedFlags |= flag; + } + + protected void demote(int flag) { + modifiedFlags &= ~flag; + } public String toSmaliString() { return toStringInternal(true);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java index 6c7caf9..0c96120 100644 --- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -50,7 +50,11 @@ } private ClassAccessFlags(int flags) { - super(flags); + this(flags, flags); + } + + private ClassAccessFlags(int originalFlags, int modifiedFlags) { + super(originalFlags, modifiedFlags); } public static ClassAccessFlags fromSharedAccessFlags(int access) { @@ -70,7 +74,7 @@ @Override public ClassAccessFlags copy() { - return new ClassAccessFlags(flags).setPromotedToPublic(isPromotedToPublic()); + return new ClassAccessFlags(originalFlags, modifiedFlags); } @Override
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 5aa2ee4..44294db 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -645,7 +645,7 @@ // for the forwarding method, as the forwarding method will copy the access flags from this, // and if different forwarding methods are created in different subclasses the first could be // final. - accessFlags.unsetFinal(); + accessFlags.demoteFromFinal(); DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name); Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER; Builder builder = builder(this); @@ -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/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java index e940269..bb39765 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -37,12 +37,16 @@ } private FieldAccessFlags(int flags) { - super(flags); + this(flags, flags); + } + + private FieldAccessFlags(int originalFlags, int modifiedFlags) { + super(originalFlags, modifiedFlags); } @Override public FieldAccessFlags copy() { - return new FieldAccessFlags(flags).setPromotedToPublic(isPromotedToPublic()); + return new FieldAccessFlags(originalFlags, modifiedFlags); } @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java index ca507b7..2173ed0 100644 --- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -54,12 +54,16 @@ } private MethodAccessFlags(int flags) { - super(flags); + this(flags, flags); + } + + private MethodAccessFlags(int originalFlags, int modifiedFlags) { + super(originalFlags, modifiedFlags); } @Override public MethodAccessFlags copy() { - return new MethodAccessFlags(flags).setPromotedToPublic(isPromotedToPublic()); + return new MethodAccessFlags(originalFlags, modifiedFlags); } @Override
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/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java index b59e3e9..72fc987 100644 --- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java +++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -128,7 +128,7 @@ return false; } lenseBuilder.add(encodedMethod.method); - accessFlags.setFinal(); + accessFlags.promoteToFinal(); accessFlags.promoteToPublic(); // Although the current method became public, it surely has the single virtual target. encodedMethod.method.setSingleVirtualMethodCache(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java index 6cf0da0..f502692 100644 --- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java +++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -92,7 +92,7 @@ // corresponding methods in this class. This might happen if we only keep this // class around for its constants. // For now, we remove the final flag to still be able to mark it abstract. - clazz.accessFlags.unsetFinal(); + clazz.accessFlags.demoteFromFinal(); } clazz.accessFlags.setAbstract(); }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java index 45ca1af..b9f7554 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1214,7 +1214,6 @@ // The bridge is now the public method serving the role of the original method, and should // reflect that this method was publicized. assert bridge.accessFlags.isPromotedToPublic(); - method.accessFlags.unsetPromotedToPublic(); } return bridge; } @@ -1409,7 +1408,6 @@ private static void makePrivate(DexEncodedMethod method) { assert !method.accessFlags.isAbstract(); - method.accessFlags.unsetPromotedToPublic(); method.accessFlags.unsetPublic(); method.accessFlags.unsetProtected(); method.accessFlags.setPrivate();
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java deleted file mode 100644 index cf6d7c0..0000000 --- a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java +++ /dev/null
@@ -1,65 +0,0 @@ -// 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.shaking.ifrule.accessrelaxation; - -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import com.android.tools.r8.TestBase; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -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 IfRuleWithAccessRelaxation extends TestBase { - - private final Backend backend; - - public IfRuleWithAccessRelaxation(Backend backend) { - this.backend = backend; - } - - @Parameters(name = "Backend: {0}") - public static Backend[] data() { - return Backend.values(); - } - - @Test - public void r8Test() throws Exception { - CodeInspector inspector = - testForR8(backend) - .addInnerClasses(IfRuleWithAccessRelaxation.class) - .addKeepRules( - "-keep class " + TestClass.class.getTypeName() + " { int field; void method(); }", - "-if class " + TestClass.class.getTypeName() + " { protected int field; }", - "-keep class " + Unused1.class.getTypeName(), - "-if class " + TestClass.class.getTypeName() + " { protected void method(); }", - "-keep class " + Unused2.class.getTypeName(), - "-allowaccessmodification") - .compile() - .inspector(); - - assertTrue(inspector.clazz(TestClass.class).isPublic()); - assertTrue(inspector.clazz(TestClass.class).uniqueMethodWithName("method").isPublic()); - assertTrue(inspector.clazz(TestClass.class).uniqueFieldWithName("field").isPublic()); - - assertThat(inspector.clazz(Unused1.class), isPresent()); - assertThat(inspector.clazz(Unused2.class), isPresent()); - } - - protected static class TestClass { - - protected int field = 42; - - protected void method() {} - } - - static class Unused1 {} - - static class Unused2 {} -}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java new file mode 100644 index 0000000..560ebd4 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
@@ -0,0 +1,97 @@ +// 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.shaking.ifrule.accessrelaxation; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic; +import static org.hamcrest.CoreMatchers.allOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +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 IfRuleWithAccessRelaxationTest extends TestBase { + + private final Backend backend; + + public IfRuleWithAccessRelaxationTest(Backend backend) { + this.backend = backend; + } + + @Parameters(name = "Backend: {0}") + public static Backend[] data() { + return Backend.values(); + } + + @Test + public void r8Test() throws Exception { + CodeInspector inspector = + testForR8(backend) + .addInnerClasses(IfRuleWithAccessRelaxationTest.class) + .addKeepMainRule(TestClass.class) + .addKeepRules( + "-keep class " + TestClass.class.getTypeName() + " { int field; }", + "-if class " + TestClass.class.getTypeName() + " { protected int field; }", + "-keep class " + Unused1.class.getTypeName(), + "-if class " + TestClass.class.getTypeName() + " {", + " private !final void privateMethod();", + "}", + "-keep class " + Unused2.class.getTypeName(), + "-if class " + TestClass.class.getTypeName() + " {", + " protected void virtualMethod();", + "}", + "-keep class " + Unused3.class.getTypeName(), + "-allowaccessmodification") + .enableInliningAnnotations() + .compile() + .inspector(); + + assertTrue(inspector.clazz(TestClass.class).isPublic()); + assertThat(inspector.clazz(TestClass.class).uniqueFieldWithName("field"), isPublic()); + assertThat( + inspector.clazz(TestClass.class).uniqueMethodWithName("privateMethod"), + allOf(isPublic(), isFinal())); + assertThat(inspector.clazz(TestClass.class).uniqueMethodWithName("virtualMethod"), isPublic()); + + assertThat(inspector.clazz(Unused1.class), isPresent()); + assertThat(inspector.clazz(Unused2.class), isPresent()); + assertThat(inspector.clazz(Unused3.class), isPresent()); + } + + protected static class TestClass { + + public static void main(String[] args) { + TestClass obj = new TestClass(); + obj.privateMethod(); + obj.virtualMethod(); + } + + protected int field = 42; + + @NeverInline + private void privateMethod() { + System.out.println("In privateMethod()"); + } + + @NeverInline + protected void virtualMethod() { + System.out.println("In virtualMethod()"); + } + } + + static class Unused1 {} + + static class Unused2 {} + + static class Unused3 {} +}
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 b2dff57..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 @@ -227,6 +249,28 @@ }; } + public static Matcher<MethodSubject> isFinal() { + return new TypeSafeMatcher<MethodSubject>() { + @Override + public boolean matchesSafely(final MethodSubject method) { + return method.isPresent() && method.isFinal(); + } + + @Override + public void describeTo(final Description description) { + description.appendText("is final"); + } + + @Override + public void describeMismatchSafely(final MethodSubject method, Description description) { + description + .appendText("method ") + .appendValue(method.getOriginalName()) + .appendText(" was not"); + } + }; + } + public static <T extends MemberSubject> Matcher<T> isPrivate() { return hasVisibility(Visibility.PRIVATE); }
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() {