[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