| // Copyright (c) 2019, 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.KotlinClassMetadataReader.toKotlinClassMetadata; |
| 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.KotlinMetadataWriter.kotlinMetadataToString; |
| |
| 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.DexType; |
| import com.android.tools.r8.graph.DexValue; |
| import com.android.tools.r8.graph.DexValue.DexValueArray; |
| import com.android.tools.r8.graph.DexValue.DexValueInt; |
| import com.android.tools.r8.graph.DexValue.DexValueString; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.utils.ConsumerUtils; |
| import com.android.tools.r8.utils.Pair; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import kotlinx.metadata.jvm.KotlinClassHeader; |
| |
| public class KotlinMetadataRewriter { |
| |
| // Due to a bug with nested classes and the lookup of RequirementVersion, we bump all metadata |
| // versions to 1.4 if compiled with kotlin 1.3 (1.1.16). For more information, see b/161885097. |
| private static final int[] METADATA_VERSION_1_4 = new int[] {1, 4, 0}; |
| |
| 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; |
| |
| private 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 static WriteMetadataFieldInfo rewriteAll() { |
| return new WriteMetadataFieldInfo(true, true, true, true, true, true, true, true); |
| } |
| } |
| |
| private final AppView<?> appView; |
| private final NamingLens lens; |
| private final DexItemFactory factory; |
| private final Kotlin kotlin; |
| |
| public KotlinMetadataRewriter(AppView<?> appView, NamingLens lens) { |
| this.appView = appView; |
| this.lens = lens; |
| this.factory = appView.dexItemFactory(); |
| this.kotlin = factory.kotlin; |
| } |
| |
| private static boolean isNotKotlinMetadata(DexAnnotation annotation, DexType kotlinMetadataType) { |
| return annotation.annotation.type != kotlinMetadataType; |
| } |
| |
| public void runForR8(ExecutorService executorService) throws ExecutionException { |
| DexType rewrittenMetadataType = appView.graphLens().lookupClassType(factory.kotlinMetadataType); |
| DexClass kotlinMetadata = appView.definitionFor(rewrittenMetadataType); |
| 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 -> { |
| KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo(); |
| if (kotlinInfo == getInvalidKotlinInfo()) { |
| // Maintain invalid kotlin info for classes. |
| return; |
| } |
| DexAnnotation oldMeta = clazz.annotations().getFirstMatching(rewrittenMetadataType); |
| // TODO(b/181103083): Consider removing if rewrittenMetadataType |
| // != factory.kotlinMetadataType |
| if (oldMeta == null |
| || kotlinInfo == getNoKotlinInfo() |
| || (appView.appInfo().hasLiveness() |
| && !appView.withLiveness().appInfo().isPinned(clazz.type))) { |
| // Remove @Metadata in DexAnnotation when there is no kotlin info and the type is not |
| // missing. |
| if (oldMeta != null) { |
| clazz.setAnnotations( |
| clazz |
| .annotations() |
| .keepIf(anno -> isNotKotlinMetadata(anno, rewrittenMetadataType))); |
| } |
| return; |
| } |
| writeKotlinInfoToAnnotation(clazz, kotlinInfo, oldMeta, writeMetadataFieldInfo); |
| }, |
| executorService); |
| } |
| |
| public void runForD8(ExecutorService executorService) throws ExecutionException { |
| if (lens.isIdentityLens()) { |
| return; |
| } |
| final WriteMetadataFieldInfo writeMetadataFieldInfo = WriteMetadataFieldInfo.rewriteAll(); |
| ThreadUtils.processItems( |
| appView.appInfo().classes(), |
| clazz -> { |
| DexAnnotation metadata = clazz.annotations().getFirstMatching(factory.kotlinMetadataType); |
| if (metadata == null) { |
| return; |
| } |
| KotlinClassLevelInfo kotlinInfo = |
| KotlinClassMetadataReader.getKotlinInfo( |
| clazz, appView, ConsumerUtils.emptyConsumer(), metadata); |
| if (kotlinInfo == getNoKotlinInfo()) { |
| return; |
| } |
| writeKotlinInfoToAnnotation(clazz, kotlinInfo, metadata, writeMetadataFieldInfo); |
| }, |
| executorService); |
| } |
| |
| private void writeKotlinInfoToAnnotation( |
| DexClass clazz, |
| KotlinClassLevelInfo kotlinInfo, |
| DexAnnotation oldMeta, |
| WriteMetadataFieldInfo writeMetadataFieldInfo) { |
| try { |
| Pair<KotlinClassHeader, Boolean> kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens); |
| // TODO(b/185756596): Remove when special handling is no longer needed. |
| if (!kotlinClassHeader.getSecond() |
| && appView.options().testing.keepMetadataInR8IfNotRewritten) { |
| // No rewrite occurred and the data is the same as before. |
| assert appView.checkForTesting( |
| () -> |
| verifyRewrittenMetadataIsEquivalent( |
| clazz.annotations().getFirstMatching(factory.kotlinMetadataType), |
| createKotlinMetadataAnnotation( |
| kotlinClassHeader.getFirst(), |
| kotlinInfo.getPackageName(), |
| getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()), |
| writeMetadataFieldInfo))); |
| return; |
| } |
| DexAnnotation newMeta = |
| createKotlinMetadataAnnotation( |
| kotlinClassHeader.getFirst(), |
| kotlinInfo.getPackageName(), |
| getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()), |
| writeMetadataFieldInfo); |
| clazz.setAnnotations(clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno)); |
| } catch (Throwable t) { |
| assert appView.checkForTesting( |
| () -> { |
| throw appView |
| .options() |
| .reporter |
| .fatalError(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t)); |
| }); |
| appView |
| .options() |
| .reporter |
| .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t)); |
| } |
| } |
| |
| 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"; |
| return true; |
| } |
| |
| 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.getReference().name == fieldName) |
| .iterator() |
| .hasNext(); |
| } |
| |
| private DexAnnotation createKotlinMetadataAnnotation( |
| KotlinClassHeader header, |
| String packageName, |
| int[] metadataVersion, |
| WriteMetadataFieldInfo writeMetadataFieldInfo) { |
| List<DexAnnotationElement> elements = new ArrayList<>(); |
| if (writeMetadataFieldInfo.writeMetadataVersion) { |
| elements.add( |
| 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()))); |
| } |
| 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 (writeMetadataFieldInfo.writeExtraString && !header.getExtraString().isEmpty()) { |
| elements.add( |
| new DexAnnotationElement( |
| kotlin.metadata.extraString, |
| new DexValueString(factory.createString(header.getExtraString())))); |
| } |
| if (writeMetadataFieldInfo.writeExtraInt && header.getExtraInt() != 0) { |
| elements.add( |
| new DexAnnotationElement( |
| kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt()))); |
| } |
| DexEncodedAnnotation encodedAnnotation = |
| new DexEncodedAnnotation( |
| factory.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY)); |
| return new DexAnnotation(DexAnnotation.VISIBILITY_RUNTIME, encodedAnnotation); |
| } |
| |
| private DexValueArray createIntArray(int[] data) { |
| DexValue[] values = new DexValue[data.length]; |
| for (int i = 0; i < data.length; i++) { |
| values[i] = DexValueInt.create(data[i]); |
| } |
| return new DexValueArray(values); |
| } |
| |
| private DexValueArray createStringArray(String[] data) { |
| DexValue[] values = new DexValue[data.length]; |
| for (int i = 0; i < data.length; i++) { |
| values[i] = new DexValueString(factory.createString(data[i])); |
| } |
| return new DexValueArray(values); |
| } |
| |
| // We are not sure that the format is <Major>-<Minor>-<Patch>, the format can be: <Major>-<Minor>. |
| private int[] getMaxVersion(int[] one, int[] other) { |
| assert one.length == 2 || one.length == 3; |
| assert other.length == 2 || other.length == 3; |
| if (one[0] != other[0]) { |
| return one[0] > other[0] ? one : other; |
| } |
| if (one[1] != other[1]) { |
| return one[1] > other[1] ? one : other; |
| } |
| int patchOne = one.length >= 3 ? one[2] : 0; |
| int patchOther = other.length >= 3 ? other[2] : 0; |
| if (patchOne != patchOther) { |
| return patchOne > patchOther ? one : other; |
| } |
| // They are equal up to patch, just return one. |
| return one; |
| } |
| } |