Skip to content

Commit

Permalink
Migrate to using Kotlin's metadata visitor API
Browse files Browse the repository at this point in the history
Using the visitor API is a bit more cumbersome but has improvements
in both memory footprint and performance since the metadata being
cached is less and the amount of data to visit is scoped to only
what Dagger is interested on.

RELNOTES=Improve memory footprint when parsing Kotlin metadata by using the visitor APIs.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=280710527
  • Loading branch information
danysantiago authored and cpovirk committed Nov 15, 2019
1 parent c19625a commit f4f6d18
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 87 deletions.
219 changes: 194 additions & 25 deletions java/dagger/internal/codegen/kotlin/KotlinMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,59 +16,73 @@

package dagger.internal.codegen.kotlin;

import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.base.Preconditions.checkArgument;
import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;

import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import dagger.internal.codegen.langmodel.DaggerElements;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import kotlin.Metadata;
import kotlinx.metadata.Flag;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.KmClassVisitor;
import kotlinx.metadata.KmExtensionType;
import kotlinx.metadata.KmPropertyExtensionVisitor;
import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmMethodSignature;
import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;

/** Data class of a TypeElement and its Kotlin metadata. */
final class KotlinMetadata {

private final TypeElement typeElement;
private final KmClass kmClass;

/**
* Kotlin metadata flag for this class.
*
* <p>Use {@link Flag.Class} to apply the right mask and obtain a specific value.
*/
private final int flags;

// Map that associates @Inject field elements with its Kotlin synthetic method for annotations.
private final Supplier<Map<VariableElement, Optional<ExecutableElement>>>
elementFieldAnnotationMethodMap;

KotlinMetadata(TypeElement typeElement, KmClass kmClass) {
private KotlinMetadata(TypeElement typeElement, int flags, List<Property> properties) {
this.typeElement = typeElement;
this.kmClass = kmClass;
this.flags = flags;
this.elementFieldAnnotationMethodMap =
Suppliers.memoize(
() -> {
Map<String, KmProperty> propertyDescriptors = new HashMap<>();
kmClass
.getProperties()
.forEach(
property -> {
JvmFieldSignature signature = JvmExtensionsKt.getFieldSignature(property);
if (signature != null) {
propertyDescriptors.put(signature.asString(), property);
}
});
Map<String, Property> propertyDescriptors =
properties.stream()
.filter(property -> property.getFieldSignature().isPresent())
.collect(
Collectors.toMap(
property -> property.getFieldSignature().get(), Function.identity()));
Map<String, ExecutableElement> methodDescriptors =
ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
.collect(
Collectors.toMap(DaggerElements::getMethodDescriptor, method -> method));
Collectors.toMap(
DaggerElements::getMethodDescriptor, Function.identity()));
return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
.filter(field -> isAnnotationPresent(field, Inject.class))
.collect(
Expand All @@ -78,8 +92,7 @@ final class KotlinMetadata {
Optional.ofNullable(
propertyDescriptors.get(
DaggerElements.getFieldDescriptor(field)))
.map(JvmExtensionsKt::getSyntheticMethodForAnnotations)
.map(JvmMethodSignature::asString)
.flatMap(Property::getMethodForAnnotationsSignature)
.map(methodDescriptors::get)));
});
}
Expand All @@ -88,17 +101,173 @@ TypeElement getTypeElement() {
return typeElement;
}

KmClass getKmClass() {
return kmClass;
}

/** Gets the synthetic method for annotations of a given @Inject annotated field element. */
Optional<ExecutableElement> getSyntheticAnnotationMethod(VariableElement fieldElement) {
checkArgument(elementFieldAnnotationMethodMap.get().containsKey(fieldElement));
return elementFieldAnnotationMethodMap.get().get(fieldElement);
}

boolean isObjectClass() {
return Flag.Class.IS_OBJECT.invoke(kmClass.getFlags());
return Flag.Class.IS_OBJECT.invoke(flags);
}

/** Parse Kotlin class metadata from a given type element * */
static Optional<KotlinMetadata> from(TypeElement typeElement) {
return metadataOf(typeElement)
.map(
metadata -> {
MetadataVisitor visitor = new MetadataVisitor();
metadata.accept(visitor);
return new KotlinMetadata(typeElement, visitor.classFlags, visitor.classProperties);
});
}

private static Optional<KotlinClassMetadata.Class> metadataOf(TypeElement typeElement) {
Optional<AnnotationMirror> metadataAnnotation =
getAnnotationMirror(typeElement, Metadata.class);
Preconditions.checkState(metadataAnnotation.isPresent());
KotlinClassHeader header =
new KotlinClassHeader(
getIntValue(metadataAnnotation.get(), "k"),
getIntArrayValue(metadataAnnotation.get(), "mv"),
getIntArrayValue(metadataAnnotation.get(), "bv"),
getStringArrayValue(metadataAnnotation.get(), "d1"),
getStringArrayValue(metadataAnnotation.get(), "d2"),
getStringValue(metadataAnnotation.get(), "xs"),
getStringValue(metadataAnnotation.get(), "pn"),
getIntValue(metadataAnnotation.get(), "xi"));
KotlinClassMetadata metadata = KotlinClassMetadata.read(header);
if (metadata == null) {
// Should only happen on Kotlin < 1.0 (i.e. metadata version < 1.1)
return Optional.empty();
}
if (metadata instanceof KotlinClassMetadata.Class) {
// TODO(user): If when we need other types of metadata then move to right method.
return Optional.of((KotlinClassMetadata.Class) metadata);
} else {
// Unsupported
return Optional.empty();
}
}

private static int getIntValue(AnnotationMirror annotation, String valueName) {
return (int) getAnnotationValue(annotation, valueName).getValue();
}

private static String getStringValue(AnnotationMirror annotation, String valueName) {
return getAnnotationValue(annotation, valueName).getValue().toString();
}

private static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) {
return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
.mapToInt(it -> (int) it.getValue())
.toArray();
}

private static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) {
return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
.map(it -> it.getValue().toString())
.toArray(String[]::new);
}

private static final class MetadataVisitor extends KmClassVisitor {

int classFlags;
List<Property> classProperties = new ArrayList<>();

@Override
public void visit(int flags, String s) {
this.classFlags = flags;
}

@Override
public KmPropertyVisitor visitProperty(
int flags, String name, int getterFlags, int setterFlags) {
return new KmPropertyVisitor() {
String fieldSignature;
String methodForAnnotationsSignature;

@Override
public KmPropertyExtensionVisitor visitExtensions(KmExtensionType kmExtensionType) {
if (!kmExtensionType.equals(JvmPropertyExtensionVisitor.TYPE)) {
return null;
}

return new JvmPropertyExtensionVisitor() {
@Override
public void visit(
int jvmFlags,
JvmFieldSignature jvmFieldSignature,
JvmMethodSignature getterSignature,
JvmMethodSignature setterSignature) {
if (jvmFieldSignature != null) {
fieldSignature = jvmFieldSignature.asString();
}
}

@Override
public void visitSyntheticMethodForAnnotations(JvmMethodSignature methodSignature) {
if (methodSignature != null) {
methodForAnnotationsSignature = methodSignature.asString();
}
}
};
}

@Override
public void visitEnd() {
classProperties.add(
new Property(
name,
flags,
Optional.ofNullable(fieldSignature),
Optional.ofNullable(methodForAnnotationsSignature)));
}
};
}
}

/* Data class representing Kotlin Property Metadata */
private static final class Property {

private final String name;
private final int flags;
private final Optional<String> fieldSignature;
private final Optional<String> methodForAnnotationsSignature;

Property(
String name,
int flags,
Optional<String> fieldSignature,
Optional<String> methodForAnnotationsSignature) {
this.name = name;
this.flags = flags;
this.fieldSignature = fieldSignature;
this.methodForAnnotationsSignature = methodForAnnotationsSignature;
}

/** Returns the simple name of this property. */
String getName() {
return name;
}

/**
* Returns the Kotlin metadata flags for this property.
*
* <p>Use {@link Flag.Property} to apply the right mask and obtain a specific value.
*/
int getFlags() {
return flags;
}

/** Returns the JVM field descriptor of the backing field of this property. */
Optional<String> getFieldSignature() {
return fieldSignature;
}

/** Returns JVM method descriptor of the synthetic method for property annotations. */
Optional<String> getMethodForAnnotationsSignature() {
return methodForAnnotationsSignature;
}
}
}
64 changes: 2 additions & 62 deletions java/dagger/internal/codegen/kotlin/KotlinMetadataFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,21 @@

package dagger.internal.codegen.kotlin;

import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues;
import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;

import com.google.common.base.Preconditions;
import dagger.internal.codegen.base.ClearableCache;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import kotlin.Metadata;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;

/**
* Factory for parsing and creating Kotlin metadata data object.
* Factory creating Kotlin metadata data objects.
*
* <p>The metadata is cache since it can be expensive to parse the information stored in a proto
* binary string format in the metadata annotation values.
Expand All @@ -55,59 +47,7 @@ public Optional<KotlinMetadata> create(Element element) {
if (!isAnnotationPresent(enclosingElement, Metadata.class)) {
return Optional.empty();
}
return metadataCache.computeIfAbsent(
enclosingElement,
typeElement ->
kmClassOf(typeElement)
.map(classMetadata -> new KotlinMetadata(typeElement, classMetadata)));
}

private static Optional<KmClass> kmClassOf(TypeElement typeElement) {
Optional<AnnotationMirror> metadataAnnotation =
getAnnotationMirror(typeElement, Metadata.class);
Preconditions.checkState(metadataAnnotation.isPresent());
KotlinClassHeader header =
new KotlinClassHeader(
getIntValue(metadataAnnotation.get(), "k"),
getIntArrayValue(metadataAnnotation.get(), "mv"),
getIntArrayValue(metadataAnnotation.get(), "bv"),
getStringArrayValue(metadataAnnotation.get(), "d1"),
getStringArrayValue(metadataAnnotation.get(), "d2"),
getStringValue(metadataAnnotation.get(), "xs"),
getStringValue(metadataAnnotation.get(), "pn"),
getIntValue(metadataAnnotation.get(), "xi"));
KotlinClassMetadata metadata = KotlinClassMetadata.read(header);
if (metadata == null) {
// Should only happen on Kotlin < 1.0 (i.e. metadata version < 1.1)
return Optional.empty();
}
if (metadata instanceof KotlinClassMetadata.Class) {
// TODO(user): If when we need other types of metadata then move to right method.
return Optional.of(((KotlinClassMetadata.Class) metadata).toKmClass());
} else {
// Unsupported
return Optional.empty();
}
}

private static int getIntValue(AnnotationMirror annotation, String valueName) {
return (int) getAnnotationValue(annotation, valueName).getValue();
}

private static String getStringValue(AnnotationMirror annotation, String valueName) {
return getAnnotationValue(annotation, valueName).getValue().toString();
}

private static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) {
return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
.mapToInt(it -> (int) it.getValue())
.toArray();
}

private static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) {
return asAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
.map(it -> it.getValue().toString())
.toArray(String[]::new);
return metadataCache.computeIfAbsent(enclosingElement, KotlinMetadata::from);
}

@Override
Expand Down

0 comments on commit f4f6d18

Please sign in to comment.