| // Copyright (c) 2020, 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; |
| |
| import static com.android.tools.r8.kotlin.KotlinMetadataUtils.INVALID_KOTLIN_INFO; |
| import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO; |
| |
| 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.DexDefinitionSupplier; |
| import com.android.tools.r8.graph.DexEncodedAnnotation; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| 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.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.function.Consumer; |
| import kotlinx.metadata.InconsistentKotlinMetadataException; |
| import kotlinx.metadata.jvm.KotlinClassHeader; |
| import kotlinx.metadata.jvm.KotlinClassMetadata; |
| |
| public final class KotlinClassMetadataReader { |
| |
| private static int KOTLIN_METADATA_KIND_LAMBDA = 3; |
| |
| public static KotlinClassLevelInfo getKotlinInfo( |
| Kotlin kotlin, |
| DexClass clazz, |
| DexDefinitionSupplier definitionSupplier, |
| Reporter reporter, |
| boolean onlyProcessLambda, |
| Consumer<DexEncodedMethod> keepByteCode) { |
| DexAnnotation meta = |
| clazz |
| .annotations() |
| .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType); |
| if (meta != null) { |
| try { |
| KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation); |
| if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) { |
| return NO_KOTLIN_INFO; |
| } |
| return createKotlinInfo( |
| kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode); |
| } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) { |
| reporter.info( |
| new StringDiagnostic( |
| "Class " |
| + clazz.type.toSourceString() |
| + " has malformed kotlin.Metadata: " |
| + e.getMessage())); |
| return INVALID_KOTLIN_INFO; |
| } catch (Throwable e) { |
| reporter.info( |
| new StringDiagnostic( |
| "Unexpected error while reading " |
| + clazz.type.toSourceString() |
| + "'s kotlin.Metadata: " |
| + e.getMessage())); |
| return INVALID_KOTLIN_INFO; |
| } |
| } |
| return NO_KOTLIN_INFO; |
| } |
| |
| public static KotlinClassMetadata toKotlinClassMetadata( |
| Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) { |
| Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>(); |
| for (DexAnnotationElement element : metadataAnnotation.elements) { |
| elementMap.put(element.name, element); |
| } |
| |
| DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind); |
| if (kind == null) { |
| throw new MetadataError("element 'k' is missing."); |
| } |
| Integer k = (Integer) kind.value.getBoxedValue(); |
| 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); |
| String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2"); |
| DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString); |
| String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs"); |
| DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName); |
| String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn"); |
| 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); |
| } |
| |
| public static KotlinClassLevelInfo createKotlinInfo( |
| Kotlin kotlin, |
| DexClass clazz, |
| KotlinClassMetadata kMetadata, |
| DexDefinitionSupplier definitionSupplier, |
| Reporter reporter, |
| Consumer<DexEncodedMethod> keepByteCode) { |
| String packageName = kMetadata.getHeader().getPackageName(); |
| if (kMetadata instanceof KotlinClassMetadata.Class) { |
| return KotlinClassInfo.create( |
| ((KotlinClassMetadata.Class) kMetadata).toKmClass(), |
| packageName, |
| clazz, |
| definitionSupplier, |
| reporter, |
| keepByteCode); |
| } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) { |
| // e.g., B.kt becomes class `BKt` |
| return KotlinFileFacadeInfo.create( |
| (KotlinClassMetadata.FileFacade) kMetadata, |
| packageName, |
| clazz, |
| definitionSupplier, |
| reporter, |
| keepByteCode); |
| } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) { |
| // multi-file class with the same @JvmName. |
| return KotlinMultiFileClassFacadeInfo.create( |
| (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier); |
| } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) { |
| // A single file, which is part of multi-file class. |
| return KotlinMultiFileClassPartInfo.create( |
| (KotlinClassMetadata.MultiFileClassPart) kMetadata, |
| packageName, |
| clazz, |
| definitionSupplier, |
| reporter, |
| keepByteCode); |
| } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) { |
| return KotlinSyntheticClassInfo.create( |
| (KotlinClassMetadata.SyntheticClass) kMetadata, |
| packageName, |
| clazz, |
| kotlin, |
| definitionSupplier, |
| reporter); |
| } else { |
| throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind()); |
| } |
| } |
| |
| private static int[] getUnboxedIntArray(DexValue v, String elementName) { |
| if (!v.isDexValueArray()) { |
| throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); |
| } |
| DexValueArray intArrayValue = v.asDexValueArray(); |
| DexValue[] values = intArrayValue.getValues(); |
| int[] result = new int [values.length]; |
| for (int i = 0; i < values.length; i++) { |
| result[i] = (Integer) values[i].getBoxedValue(); |
| } |
| return result; |
| } |
| |
| private static String[] getUnboxedStringArray(DexValue v, String elementName) { |
| if (!v.isDexValueArray()) { |
| throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); |
| } |
| DexValueArray stringArrayValue = v.asDexValueArray(); |
| DexValue[] values = stringArrayValue.getValues(); |
| String[] result = new String [values.length]; |
| for (int i = 0; i < values.length; i++) { |
| result[i] = getUnboxedString(values[i], elementName + "[" + i + "]"); |
| } |
| return result; |
| } |
| |
| private static String getUnboxedString(DexValue v, String elementName) { |
| if (!v.isDexValueString()) { |
| throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString()); |
| } |
| return v.asDexValueString().getValue().toString(); |
| } |
| |
| private static class MetadataError extends RuntimeException { |
| MetadataError(String cause) { |
| super(cause); |
| } |
| } |
| } |