blob: 254255e744cd0d429a09134e9776accc7f43791b [file] [log] [blame]
// 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.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.shaking.Enqueuer;
import com.google.common.collect.Sets;
import java.util.Set;
public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
private final AppView<?> appView;
private final DexDefinitionSupplier definitionSupplier;
public KotlinMetadataEnqueuerExtension(
AppView<?> appView, DexDefinitionSupplier definitionSupplier, Set<DexType> prunedTypes) {
this.appView = appView;
this.definitionSupplier = new KotlinMetadataDefinitionSupplier(definitionSupplier, prunedTypes);
}
@Override
public void done(Enqueuer enqueuer) {
DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
DexClass kotlinMetadataClass =
appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
// In the first round of tree shaking build up all metadata such that it can be traced later.
boolean keepMetadata =
kotlinMetadataClass == null
|| kotlinMetadataClass.isNotProgramClass()
|| enqueuer.isPinned(kotlinMetadataType);
if (enqueuer.getMode().isInitialTreeShaking()) {
Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet();
enqueuer.forAllLiveClasses(
clazz -> {
boolean onlyProcessLambdas = !keepMetadata || !enqueuer.isPinned(clazz.type);
assert clazz.getKotlinInfo().isNoKotlinInformation();
clazz.setKotlinInfo(
KotlinClassMetadataReader.getKotlinInfo(
appView.dexItemFactory().kotlin,
clazz,
definitionSupplier.dexItemFactory(),
appView.options().reporter,
onlyProcessLambdas,
method -> keepByteCodeFunctions.add(method.method)));
if (clazz.getEnclosingMethod() != null
&& clazz.getEnclosingMethod().getEnclosingMethod() != null) {
localOrAnonymousClasses.add(clazz);
}
});
appView.setCfByteCodePassThrough(keepByteCodeFunctions);
for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
EnclosingMethodAttribute enclosingAttribute = localOrAnonymousClass.getEnclosingMethod();
DexClass holder =
definitionSupplier.definitionForHolder(enclosingAttribute.getEnclosingMethod());
if (holder == null) {
continue;
}
DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod());
// If we cannot lookup the method, the conservative choice is keep the byte code.
if (method == null
|| (method.getKotlinMemberInfo().isFunction()
&& method.getKotlinMemberInfo().asFunction().hasCrossInlineParameter())) {
localOrAnonymousClass.forEachProgramMethod(
m -> keepByteCodeFunctions.add(m.getReference()));
}
}
} else {
assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata);
}
// Trace through the modeled kotlin metadata.
enqueuer.forAllLiveClasses(
clazz -> {
clazz.getKotlinInfo().trace(definitionSupplier);
forEachApply(
clazz.methods(), method -> method.getKotlinMemberInfo()::trace, definitionSupplier);
forEachApply(
clazz.fields(), field -> field.getKotlinMemberInfo()::trace, definitionSupplier);
});
}
private boolean verifyKotlinMetadataModeledForAllClasses(
Enqueuer enqueuer, boolean keepMetadata) {
enqueuer.forAllLiveClasses(
clazz -> {
// Trace through class and member definitions
assert !hasKotlinClassMetadataAnnotation(clazz, definitionSupplier)
|| !keepMetadata
|| !enqueuer.isPinned(clazz.type)
|| clazz.getKotlinInfo() != NO_KOTLIN_INFO;
});
return true;
}
public static class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
private final DexDefinitionSupplier baseSupplier;
private final Set<DexType> prunedTypes;
private KotlinMetadataDefinitionSupplier(
DexDefinitionSupplier baseSupplier, Set<DexType> prunedTypes) {
this.baseSupplier = baseSupplier;
this.prunedTypes = prunedTypes;
}
@Override
public DexDefinition definitionFor(DexReference reference) {
throw new Unreachable("Should not be called");
}
@Override
public DexEncodedField definitionFor(DexField field) {
throw new Unreachable("Should not be called");
}
@Override
public DexEncodedMethod definitionFor(DexMethod method) {
throw new Unreachable("Should not be called");
}
@Override
public DexClass definitionFor(DexType type) {
// TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type
// has been pruned it may still be referenced, so we do an early check here to ensure it will
// not end up as. Ideally, those types should be removed by a pass on the modeled data.
if (prunedTypes != null && prunedTypes.contains(type)) {
return null;
}
return baseSupplier.definitionFor(type);
}
@Override
public DexProgramClass definitionForProgramType(DexType type) {
throw new Unreachable("Should not be called");
}
@Override
public DexItemFactory dexItemFactory() {
return baseSupplier.dexItemFactory();
}
}
}