Reproduce b/159987443

Keep rule for static interface method does not keep companion class and
implementation when desugaring.

Bug: 159987443
Change-Id: Icc12d41bf3c5075b9874b4e8a3a6f014411ddd03
diff --git a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
new file mode 100644
index 0000000..ed6d43b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
@@ -0,0 +1,234 @@
+// 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.staticinterfacemethods.defaultmethods;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+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.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+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 com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StaticInterfaceMethodsTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean allowObfuscation;
+
+  @Parameterized.Parameters(name = "{0}, allowObfuscation: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public StaticInterfaceMethodsTest(TestParameters parameters, boolean allowObfuscation) {
+    this.parameters = parameters;
+    this.allowObfuscation = allowObfuscation;
+  }
+
+  private R8TestCompileResult compileTest(
+      List<String> additionalKeepRules,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspection)
+      throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(InterfaceWithStaticMethods.class, TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(additionalKeepRules)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(inspection);
+  }
+
+  private void runTest(
+      List<String> additionalKeepRules,
+      ThrowingConsumer<CodeInspector, RuntimeException> inspection)
+      throws Exception {
+    R8TestCompileResult compileResult = compileTest(additionalKeepRules, inspection);
+    Path app = compileResult.writeToZip();
+
+    TestRunResult<?> result;
+    if (allowObfuscation) {
+      result =
+          testForR8(parameters.getBackend())
+              .addProgramClasses(InstrumentedTestClass.class)
+              .addKeepAllClassesRule()
+              .addApplyMapping(compileResult.getProguardMap())
+              .addClasspathClasses(InterfaceWithStaticMethods.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .addRunClasspathFiles(app)
+              .run(parameters.getRuntime(), InstrumentedTestClass.class);
+    } else {
+      if (parameters.getRuntime().isCf()) {
+        result =
+            testForJvm()
+                .addProgramClasses(InstrumentedTestClass.class)
+                .addRunClasspathFiles(app)
+                .run(parameters.getRuntime(), InstrumentedTestClass.class);
+      } else {
+        result =
+            testForD8()
+                .addProgramClasses(InstrumentedTestClass.class)
+                .addClasspathClasses(InterfaceWithStaticMethods.class)
+                .setMinApi(parameters.getApiLevel())
+                .compile()
+                .addRunClasspathFiles(app)
+                .run(parameters.getRuntime(), InstrumentedTestClass.class);
+      }
+    }
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertSuccessWithOutputLines("42");
+    } else {
+      result.assertFailure();
+    }
+  }
+
+  private void interfaceNotKept(CodeInspector inspector) {
+    assertFalse(inspector.clazz(InterfaceWithStaticMethods.class).isPresent());
+  }
+
+  private void staticMethodNotKept(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(InterfaceWithStaticMethods.class);
+    assertTrue(clazz.isPresent());
+    assertFalse(clazz.method("int", "method", ImmutableList.of()).isPresent());
+  }
+
+  private void staticMethodKeptB159987443(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(InterfaceWithStaticMethods.class);
+    assertThat(clazz, isPresent());
+    MethodSubject method = clazz.method("int", "method", ImmutableList.of());
+    ClassSubject companionClass = clazz.toCompanionClass();
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      assertThat(method, isStatic());
+      assertThat(companionClass, not(isPresent()));
+    } else {
+      assertThat(method, not(isPresent()));
+      // TODO(159987443): The companion class should be present.
+      assertThat(companionClass, not(isPresent()));
+      // Also check that method exists on companion class.
+    }
+  }
+
+  private void staticMethodKept(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(InterfaceWithStaticMethods.class);
+    ClassSubject companionClass = clazz.toCompanionClass();
+    MethodSubject method = clazz.method("int", "method", ImmutableList.of());
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      assertThat(clazz, allowObfuscation ? isRenamed() : allOf(isPresent(), not(isRenamed())));
+      assertThat(method, isStatic());
+      assertThat(companionClass, not(isPresent()));
+    } else {
+      // When there is only a static method in the interface nothing is left on the interface itself
+      // after desugaring, only the companion class is left.
+      assertThat(clazz, not(isPresent()));
+      assertThat(method, not(isPresent()));
+      // TODO(159987443): The companion class should be present.
+      assertThat(companionClass, not(isPresent()));
+      // Also check that method exists on companion class.
+    }
+  }
+
+  @Test
+  public void testInterfaceNotKept() throws Exception {
+    assumeTrue(!allowObfuscation); // No use of allowObfuscation.
+
+    compileTest(ImmutableList.of(), this::interfaceNotKept);
+  }
+
+  @Test
+  public void testStaticMethodNotKept() throws Exception {
+    assumeTrue(!allowObfuscation); // No use of allowObfuscation.
+
+    compileTest(
+        ImmutableList.of(
+            "-keep interface " + InterfaceWithStaticMethods.class.getTypeName() + "{", "}"),
+        this::staticMethodNotKept);
+  }
+
+  @Test
+  public void testDefaultMethodKeptWithMethods() throws Exception {
+    assumeTrue(!allowObfuscation); // No use of allowObfuscation.
+
+    compileTest(
+        ImmutableList.of(
+            "-keep interface " + InterfaceWithStaticMethods.class.getTypeName() + "{",
+            "  <methods>;",
+            "}"),
+        this::staticMethodKeptB159987443);
+  }
+
+  @Test
+  public void testDefaultMethodKeptIndirectly() throws Exception {
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+    builder.add(
+        "-keep class " + TestClass.class.getTypeName() + "{",
+        "  public void useStaticInterfaceMethod();",
+        "}");
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      // TODO(160142903): @NeverInline does not seem to work on static interface methods.
+      // TODO(160144053): Using -keepclassmembers for this cause InterfaceWithStaticMethods to
+      // be renamed.
+      if (allowObfuscation) {
+        builder.add(
+            "-if class " + InterfaceWithStaticMethods.class.getTypeName(),
+            "-keep,allowobfuscation class " + InterfaceWithStaticMethods.class.getTypeName() + "{",
+            "  public static int method();",
+            "}");
+      } else {
+        builder.add(
+            "-if class " + InterfaceWithStaticMethods.class.getTypeName(),
+            "-keep class " + InterfaceWithStaticMethods.class.getTypeName() + "{",
+            "  public static int method();",
+            "}");
+      }
+    }
+    runTest(builder.build(), this::staticMethodKept);
+  }
+
+  public static class TestClass {
+
+    public void useStaticInterfaceMethod() {
+      System.out.println(InterfaceWithStaticMethods.method());
+    }
+
+    public static void main(String[] args) {}
+  }
+
+  public interface InterfaceWithStaticMethods {
+    @NeverInline
+    static int method() {
+      return 42;
+    }
+  }
+
+  public static class InstrumentedTestClass {
+
+    public static void main(String[] args) {
+      System.out.println(InterfaceWithStaticMethods.method());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index f872a70..6a04a0a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -177,9 +177,4 @@
   public KotlinClassMetadata getKotlinClassMetadata() {
     return null;
   }
-
-  @Override
-  public ClassSubject toCompanionClass() {
-    throw new Unreachable("Cannot determine EnclosingMethod attribute of an absent class");
-  }
 }