blob: a094a882cef4080e9fc93c7c13dd122a52956db0 [file] [log] [blame]
// 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.enableWholeProgramOptimizations()) {
// 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) {
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;
}
}