[Metadata] Report exceptions from parsing kotlin metadata

Bug: b/235183512
Change-Id: I435456a6e9c1d153b3c95a034c490c555456b1e2
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 7f4c47d..eb07f4b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour;
 
@@ -19,7 +18,6 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour;
-import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -36,7 +34,8 @@
   private static final int SYNTHETIC_CLASS_KIND = 3;
 
   public static KotlinClassLevelInfo getKotlinInfo(
-      DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) {
+      DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode)
+      throws KotlinMetadataException {
     DexAnnotation meta =
         clazz.annotations().getFirstMatching(appView.dexItemFactory().kotlinMetadataType);
     return meta != null ? getKotlinInfo(clazz, appView, keepByteCode, meta) : getNoKotlinInfo();
@@ -46,35 +45,18 @@
       DexClass clazz,
       AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode,
-      DexAnnotation annotation) {
-    try {
-      Kotlin kotlin = appView.dexItemFactory().kotlin;
-      KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
-      return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode);
-    } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
-      appView
-          .reporter()
-          .info(
-              new StringDiagnostic(
-                  "Class "
-                      + clazz.type.toSourceString()
-                      + " has malformed kotlin.Metadata: "
-                      + e.getMessage()));
-      return getInvalidKotlinInfo();
-    } catch (Throwable e) {
-      appView
-          .reporter()
-          .info(
-              new StringDiagnostic(
-                  "Unexpected error while reading "
-                      + clazz.type.toSourceString()
-                      + "'s kotlin.Metadata: "
-                      + e.getMessage()));
-      return getNoKotlinInfo();
+      DexAnnotation annotation)
+      throws KotlinMetadataException {
+    Kotlin kotlin = appView.dexItemFactory().kotlin;
+    KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
+    if (kMetadata == null) {
+      throw new KotlinMetadataException();
     }
+    return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode);
   }
 
-  public static boolean isLambda(AppView<?> appView, DexClass clazz) {
+  public static boolean isLambda(AppView<?> appView, DexClass clazz)
+      throws KotlinMetadataException {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     Kotlin kotlin = dexItemFactory.kotlin;
     Flavour flavour = getFlavour(clazz, kotlin);
@@ -108,7 +90,7 @@
   }
 
   public static KotlinClassMetadata toKotlinClassMetadata(
-      Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
+      Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) throws KotlinMetadataException {
     return toKotlinClassMetadata(kotlin, toElementMap(metadataAnnotation));
   }
 
@@ -122,12 +104,11 @@
   }
 
   private static KotlinClassMetadata toKotlinClassMetadata(
-      Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) {
+      Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap)
+      throws KotlinMetadataException {
     int k = getKind(kotlin, elementMap);
     DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
     int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
-    DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
-    int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv");
     DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1);
     String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1");
     DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2);
@@ -139,8 +120,12 @@
     DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt);
     Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
 
-    KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
-    return KotlinClassMetadata.read(header);
+    try {
+      KotlinClassHeader header = new KotlinClassHeader(k, mv, d1, d2, xs, pn, xi);
+      return KotlinClassMetadata.read(header);
+    } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
+      throw new KotlinMetadataException(e);
+    }
   }
 
   private static int getKind(Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
index 83947d7..66dc1be 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -13,6 +13,9 @@
 
 public class KotlinMetadataDiagnostic implements Diagnostic {
 
+  private static final String LINK_TO_PAGE =
+      "https://developer.android.com/studio/build/kotlin-d8-r8-versions";
+
   private final Origin origin;
   private final Position position;
   private final String message;
@@ -73,4 +76,14 @@
             + StringUtils.LINE_SEPARATOR
             + StringUtils.stacktraceAsString(t));
   }
+
+  static KotlinMetadataDiagnostic unknownMetadataVersion() {
+    return new KotlinMetadataDiagnostic(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        "An error occurred when parsing kotlin metadata. This normally happens when using a newer"
+            + " version of kotlin than the kotlin version released when this version of R8 was"
+            + " created. To find compatible kotlin versions, please see: "
+            + LINK_TO_PAGE);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 2a3698b..6b5d589 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -36,6 +38,7 @@
   private final AppView<?> appView;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
   private final Set<DexType> prunedTypes;
+  private boolean reportedUnknownMetadataVersion;
 
   public KotlinMetadataEnqueuerExtension(
       AppView<?> appView,
@@ -66,26 +69,52 @@
       enqueuer.forAllLiveClasses(
           clazz -> {
             assert clazz.getKotlinInfo().isNoKotlinInformation();
-            if (enqueuer
-                .getKeepInfo(clazz)
-                .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
-              if (KotlinClassMetadataReader.isLambda(appView, clazz)
-                  && clazz.hasClassInitializer()) {
-                feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
+            try {
+              if (enqueuer
+                  .getKeepInfo(clazz)
+                  .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
+                if (KotlinClassMetadataReader.isLambda(appView, clazz)
+                    && clazz.hasClassInitializer()) {
+                  feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
+                }
+                clazz.clearKotlinInfo();
+                clazz.removeAnnotations(
+                    annotation ->
+                        annotation.getAnnotationType()
+                            == appView.dexItemFactory().kotlinMetadataType);
+              } else {
+                clazz.setKotlinInfo(
+                    KotlinClassMetadataReader.getKotlinInfo(
+                        clazz,
+                        appView,
+                        method -> keepByteCodeFunctions.add(method.getReference())));
+                if (clazz.getEnclosingMethodAttribute() != null
+                    && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+                  localOrAnonymousClasses.add(clazz);
+                }
               }
-              clazz.clearKotlinInfo();
-              clazz.removeAnnotations(
-                  annotation ->
-                      annotation.getAnnotationType()
-                          == appView.dexItemFactory().kotlinMetadataType);
-            } else {
-              clazz.setKotlinInfo(
-                  KotlinClassMetadataReader.getKotlinInfo(
-                      clazz, appView, method -> keepByteCodeFunctions.add(method.getReference())));
-              if (clazz.getEnclosingMethodAttribute() != null
-                  && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
-                localOrAnonymousClasses.add(clazz);
-              }
+            } catch (KotlinMetadataException e) {
+              appView
+                  .reporter()
+                  .info(
+                      new StringDiagnostic(
+                          "Class "
+                              + clazz.type.toSourceString()
+                              + " has malformed kotlin.Metadata: "
+                              + e.getMessage()));
+              clazz.setKotlinInfo(getInvalidKotlinInfo());
+              reportUnknownMetadataVersion();
+            } catch (Throwable e) {
+              appView
+                  .reporter()
+                  .info(
+                      new StringDiagnostic(
+                          "Unexpected error while reading "
+                              + clazz.type.toSourceString()
+                              + "'s kotlin.Metadata: "
+                              + e.getMessage()));
+              clazz.setKotlinInfo(getNoKotlinInfo());
+              reportUnknownMetadataVersion();
             }
           });
       for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
@@ -138,6 +167,13 @@
         });
   }
 
+  private void reportUnknownMetadataVersion() {
+    if (!reportedUnknownMetadataVersion) {
+      reportedUnknownMetadataVersion = true;
+      appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion());
+    }
+  }
+
   public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
 
     private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java
new file mode 100644
index 0000000..4ed669e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataException.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, 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.kotlin;
+
+/** Exception class to ensure that exceptions arising from kotlin metadata parsing are handled */
+public class KotlinMetadataException extends Exception {
+
+  KotlinMetadataException() {}
+
+  KotlinMetadataException(Throwable 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 5353319..415995e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -195,11 +195,15 @@
 
   private boolean verifyRewrittenMetadataIsEquivalent(
       DexAnnotation original, DexAnnotation rewritten) {
-    String originalMetadata =
-        kotlinMetadataToString("", toKotlinClassMetadata(kotlin, original.annotation));
-    String rewrittenMetadata =
-        kotlinMetadataToString("", toKotlinClassMetadata(kotlin, rewritten.annotation));
-    assert originalMetadata.equals(rewrittenMetadata) : "The metadata should be equivalent";
+    try {
+      String originalMetadata =
+          kotlinMetadataToString("", toKotlinClassMetadata(kotlin, original.annotation));
+      String rewrittenMetadata =
+          kotlinMetadataToString("", toKotlinClassMetadata(kotlin, rewritten.annotation));
+      assert originalMetadata.equals(rewrittenMetadata) : "The metadata should be equivalent";
+    } catch (KotlinMetadataException ignored) {
+
+    }
     return true;
   }
 
@@ -228,11 +232,6 @@
           new DexAnnotationElement(
               kotlin.metadata.metadataVersion, createIntArray(metadataVersion)));
     }
-    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())));
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 cb65261..2c304ad 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
+import com.android.tools.r8.kotlin.KotlinMetadataException;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -544,9 +545,14 @@
     if (!annotationSubject.isPresent()) {
       return new AbsentKmClassSubject();
     }
-    KotlinClassMetadata metadata =
-        KotlinClassMetadataReader.toKotlinClassMetadata(
-            codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    KotlinClassMetadata metadata = null;
+    try {
+      metadata =
+          KotlinClassMetadataReader.toKotlinClassMetadata(
+              codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    } catch (KotlinMetadataException e) {
+      throw new RuntimeException(e);
+    }
     assertTrue(metadata instanceof KotlinClassMetadata.Class);
     KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) metadata;
     return new FoundKmClassSubject(codeInspector, getDexProgramClass(), kClass.toKmClass());
@@ -558,9 +564,14 @@
     if (!annotationSubject.isPresent()) {
       return new AbsentKmPackageSubject();
     }
-    KotlinClassMetadata metadata =
-        KotlinClassMetadataReader.toKotlinClassMetadata(
-            codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    KotlinClassMetadata metadata = null;
+    try {
+      metadata =
+          KotlinClassMetadataReader.toKotlinClassMetadata(
+              codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    } catch (KotlinMetadataException e) {
+      throw new RuntimeException(e);
+    }
     assertTrue(metadata instanceof KotlinClassMetadata.FileFacade
         || metadata instanceof KotlinClassMetadata.MultiFileClassPart);
     if (metadata instanceof KotlinClassMetadata.FileFacade) {
@@ -579,8 +590,12 @@
     if (!annotationSubject.isPresent()) {
       return null;
     }
-    return KotlinClassMetadataReader.toKotlinClassMetadata(
-        codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    try {
+      return KotlinClassMetadataReader.toKotlinClassMetadata(
+          codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
+    } catch (KotlinMetadataException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   @Override