Only emit kotlin metadata values for non-pruned annotation members

Bug: 161230424
Bug: 162900580
Change-Id: Ib74e75e89577626b0b1e511011f4e018bb9c9795
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 2171a00..281950c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -188,8 +188,8 @@
     return v.asDexValueString().getValue().toString();
   }
 
-  private static class MetadataError extends RuntimeException {
-    MetadataError(String cause) {
+  public static class MetadataError extends RuntimeException {
+    private MetadataError(String cause) {
       super(cause);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index d709ea4..5e11d43 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
@@ -25,6 +27,36 @@
 
 public class KotlinMetadataRewriter {
 
+  private static final class WriteMetadataFieldInfo {
+    final boolean writeKind;
+    final boolean writeMetadataVersion;
+    final boolean writeByteCodeVersion;
+    final boolean writeData1;
+    final boolean writeData2;
+    final boolean writeExtraString;
+    final boolean writePackageName;
+    final boolean writeExtraInt;
+
+    public WriteMetadataFieldInfo(
+        boolean writeKind,
+        boolean writeMetadataVersion,
+        boolean writeByteCodeVersion,
+        boolean writeData1,
+        boolean writeData2,
+        boolean writeExtraString,
+        boolean writePackageName,
+        boolean writeExtraInt) {
+      this.writeKind = writeKind;
+      this.writeMetadataVersion = writeMetadataVersion;
+      this.writeByteCodeVersion = writeByteCodeVersion;
+      this.writeData1 = writeData1;
+      this.writeData2 = writeData2;
+      this.writeExtraString = writeExtraString;
+      this.writePackageName = writePackageName;
+      this.writeExtraInt = writeExtraInt;
+    }
+  }
+
   private final AppView<?> appView;
   private final NamingLens lens;
   private final DexItemFactory factory;
@@ -42,6 +74,18 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
+    final DexClass kotlinMetadata =
+        appView.definitionFor(appView.dexItemFactory().kotlinMetadataType);
+    final WriteMetadataFieldInfo writeMetadataFieldInfo =
+        new WriteMetadataFieldInfo(
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.kind),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.metadataVersion),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.bytecodeVersion),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.data1),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.data2),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.extraString),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.packageName),
+            kotlinMetadataFieldExists(kotlinMetadata, appView, kotlin.metadata.extraInt));
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
@@ -66,7 +110,8 @@
           try {
             KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
             DexAnnotation newMeta =
-                createKotlinMetadataAnnotation(kotlinClassHeader, kotlinInfo.getPackageName());
+                createKotlinMetadataAnnotation(
+                    kotlinClassHeader, kotlinInfo.getPackageName(), writeMetadataFieldInfo);
             clazz.setAnnotations(
                 clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
           } catch (Throwable t) {
@@ -79,33 +124,54 @@
         executorService);
   }
 
+  private boolean kotlinMetadataFieldExists(
+      DexClass kotlinMetadata, AppView<?> appView, DexString fieldName) {
+    if (!appView.appInfo().hasLiveness()) {
+      return true;
+    }
+    if (kotlinMetadata == null || kotlinMetadata.isNotProgramClass()) {
+      return true;
+    }
+    return kotlinMetadata.methods(method -> method.method.name == fieldName).iterator().hasNext();
+  }
+
   private DexAnnotation createKotlinMetadataAnnotation(
-      KotlinClassHeader header, String packageName) {
+      KotlinClassHeader header, String packageName, WriteMetadataFieldInfo writeMetadataFieldInfo) {
     List<DexAnnotationElement> elements = new ArrayList<>();
-    elements.add(
-        new DexAnnotationElement(
-            kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
-    elements.add(
-        new DexAnnotationElement(
-            kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
-    elements.add(
-        new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
-    elements.add(
-        new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
-    elements.add(
-        new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
-    if (packageName != null && !packageName.isEmpty()) {
+    if (writeMetadataFieldInfo.writeMetadataVersion) {
+      elements.add(
+          new DexAnnotationElement(
+              kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
+    }
+    if (writeMetadataFieldInfo.writeByteCodeVersion) {
+      elements.add(
+          new DexAnnotationElement(
+              kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
+    }
+    if (writeMetadataFieldInfo.writeKind) {
+      elements.add(
+          new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+    }
+    if (writeMetadataFieldInfo.writeData1) {
+      elements.add(
+          new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
+    }
+    if (writeMetadataFieldInfo.writeData2) {
+      elements.add(
+          new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
+    }
+    if (writeMetadataFieldInfo.writePackageName && packageName != null && !packageName.isEmpty()) {
       elements.add(
           new DexAnnotationElement(
               kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
     }
-    if (!header.getExtraString().isEmpty()) {
+    if (writeMetadataFieldInfo.writeExtraString && !header.getExtraString().isEmpty()) {
       elements.add(
           new DexAnnotationElement(
               kotlin.metadata.extraString,
               new DexValueString(factory.createString(header.getExtraString()))));
     }
-    if (header.getExtraInt() != 0) {
+    if (writeMetadataFieldInfo.writeExtraInt && header.getExtraInt() != 0) {
       elements.add(
           new DexAnnotationElement(
               kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
index 41a60a4..77134d8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
@@ -6,15 +6,12 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.metadata.metadata_pruned_fields.Main;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -61,43 +58,28 @@
 
   @Test
   public void testR8() throws Exception {
-    final R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-            .addProgramFiles(libJars.get(targetVersion))
-            .addProgramClassFileData(Main.dump())
-            .addKeepRules("-keep class " + PKG + ".metadata_pruned_fields.MethodsKt { *; }")
-            .addKeepRules("-keep class kotlin.Metadata { *** pn(); }")
-            .addKeepMainRule(Main.class)
-            .allowDiagnosticWarningMessages()
-            .setMinApi(parameters.getApiLevel())
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .compile()
-            .inspect(
-                codeInspector -> {
-                  final ClassSubject clazz = codeInspector.clazz("kotlin.Metadata");
-                  assertThat(clazz, isPresent());
-                  assertThat(clazz.uniqueMethodWithName("pn"), isPresent());
-                  assertThat(clazz.uniqueMethodWithName("d1"), not(isPresent()));
-                  assertThat(clazz.uniqueMethodWithName("d2"), not(isPresent()));
-                  assertThat(clazz.uniqueMethodWithName("bv"), not(isPresent()));
-                })
-            .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
-            .run(parameters.getRuntime(), Main.class);
-    if (parameters.getRuntime().isCf()) {
-      runResult.assertSuccessWithOutputLines("", "Hello World!");
-      return;
-    }
-    final DexVm vm = parameters.getRuntime().asDex().getVm();
-    if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
-      runResult.assertFailureWithErrorThatMatches(
-          containsString("java.lang.RuntimeException: failure in processEncodedAnnotation"));
-    } else if (vm.isOlderThanOrEqual(DexVm.ART_6_0_1_HOST)) {
-      runResult.assertFailureWithErrorThatMatches(
-          containsString("java.lang.IncompatibleClassChangeError: Couldn't find kotlin.Metadata."));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
-    }
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(libJars.get(targetVersion))
+        .addProgramClassFileData(Main.dump())
+        .addKeepRules("-keep class " + PKG + ".metadata_pruned_fields.MethodsKt { *; }")
+        .addKeepRules("-keep class kotlin.Metadata { *** pn(); }")
+        .addKeepMainRule(Main.class)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(
+            codeInspector -> {
+              final ClassSubject clazz = codeInspector.clazz("kotlin.Metadata");
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueMethodWithName("pn"), isPresent());
+              assertThat(clazz.uniqueMethodWithName("d1"), not(isPresent()));
+              assertThat(clazz.uniqueMethodWithName("d2"), not(isPresent()));
+              assertThat(clazz.uniqueMethodWithName("bv"), not(isPresent()));
+            })
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("", "Hello World!");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
index bb77f38..8be744d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -101,7 +101,7 @@
         .addProgramFiles(inputJarMap.get(targetVersion))
         .addKeepRules(OBFUSCATE_RENAMED, KEEP_KEPT)
         .addKeepRules("-keep class **.Anno")
-        .addKeepRules("-keep class kotlin.Metadata")
+        .addKeepKotlinMetadata()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 295f26a..3986b38 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinClassMetadataReader.MetadataError;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.util.Collection;
@@ -51,55 +49,19 @@
   }
 
   @Test
-  public void testR8KeepPartial() throws Exception {
-    // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
-    // class, but still be able to fully read the kotlin.Metadata.
-    testForR8(parameters.getBackend())
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-        .setMinApi(parameters.getApiLevel())
-        .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-        .compile()
-        .inspect(
-            inspector -> {
-              inspect(inspector);
-              ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
-              assertThat(kotlinMetadataClass, isPresent());
-              assertEquals(1, kotlinMetadataClass.allMethods().size());
-              assertNotNull(kotlinMetadataClass.getKmClass().getName());
-            });
-  }
-
-  @Test
-  public void testR8KeepPartialCooking() throws Exception {
-    // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
-    // class, but still be able to fully read the kotlin.Metadata externally.
-    testForR8(parameters.getBackend())
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-        .setMinApi(parameters.getApiLevel())
-        .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
-        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-        .compile()
-        .inspect(
-            inspector -> {
-              inspect(inspector);
-              ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
-              assertThat(kotlinMetadataClass, isPresent());
-              assertEquals(1, kotlinMetadataClass.allMethods().size());
-              assertNotNull(kotlinMetadataClass.getKmClass().getName());
-            });
-  }
-
-  @Test
   public void testR8KeepIf() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .setMinApi(parameters.getApiLevel())
         .addKeepRules("-keep class kotlin.io.** { *; }")
-        .addKeepRules("-if class * { *** $VALUES; }", "-keep class kotlin.Metadata { *; }")
+        .addKeepRules("-if class *", "-keep class kotlin.Metadata { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .compile()
-        .inspect(this::inspect);
+        .inspect(
+            codeInspector -> {
+              // TODO(b/162900580): This should be kept or the test refined.
+              assertThrows(MetadataError.class, () -> inspect(codeInspector));
+            });
   }
 
   private void inspect(CodeInspector inspector) {