1 /* 2 * Copyright (C) 2022 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.processor.internal.kotlin; 18 19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; 20 import static kotlinx.metadata.Flag.ValueParameter.DECLARES_DEFAULT_VALUE; 21 22 import androidx.room.compiler.processing.XAnnotation; 23 import androidx.room.compiler.processing.XFieldElement; 24 import androidx.room.compiler.processing.XMethodElement; 25 import androidx.room.compiler.processing.XTypeElement; 26 import com.google.auto.value.AutoValue; 27 import com.google.auto.value.extension.memoized.Memoized; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableMap; 30 import com.google.common.collect.ImmutableSet; 31 import dagger.hilt.processor.internal.ClassNames; 32 import dagger.internal.codegen.extension.DaggerCollectors; 33 import dagger.internal.codegen.xprocessing.XElements; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.Optional; 38 import java.util.function.Function; 39 import javax.annotation.Nullable; 40 import kotlin.Metadata; 41 import kotlinx.metadata.Flag; 42 import kotlinx.metadata.KmClass; 43 import kotlinx.metadata.KmConstructor; 44 import kotlinx.metadata.KmFunction; 45 import kotlinx.metadata.KmProperty; 46 import kotlinx.metadata.jvm.JvmExtensionsKt; 47 import kotlinx.metadata.jvm.JvmFieldSignature; 48 import kotlinx.metadata.jvm.JvmMetadataUtil; 49 import kotlinx.metadata.jvm.JvmMethodSignature; 50 import kotlinx.metadata.jvm.KotlinClassMetadata; 51 52 /** Data class of a TypeElement and its Kotlin metadata. */ 53 @AutoValue 54 abstract class KotlinMetadata { 55 // Kotlin suffix for fields that are for a delegated property. 56 // See: 57 // https://github.com/JetBrains/kotlin/blob/master/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAbi.kt#L32 58 private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate"; 59 60 // Map that associates field elements with its Kotlin synthetic method for annotations. 61 private final Map<XFieldElement, Optional<MethodForAnnotations>> elementFieldAnnotationMethodMap = 62 new HashMap<>(); 63 64 // Map that associates field elements with its Kotlin getter method. 65 private final Map<XFieldElement, Optional<XMethodElement>> elementFieldGetterMethodMap = 66 new HashMap<>(); 67 typeElement()68 abstract XTypeElement typeElement(); 69 classMetadata()70 abstract ClassMetadata classMetadata(); 71 72 @Memoized methodDescriptors()73 ImmutableMap<String, XMethodElement> methodDescriptors() { 74 return typeElement().getDeclaredMethods().stream() 75 .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity())); 76 } 77 78 /** Returns true if any constructor of the defined a default parameter. */ 79 @Memoized containsConstructorWithDefaultParam()80 boolean containsConstructorWithDefaultParam() { 81 return classMetadata().constructors().stream() 82 .flatMap(constructor -> constructor.parameters().stream()) 83 .anyMatch(parameter -> parameter.flags(DECLARES_DEFAULT_VALUE)); 84 } 85 86 /** Gets the synthetic method for annotations of a given field element. */ getSyntheticAnnotationMethod(XFieldElement fieldElement)87 Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) { 88 return getAnnotationMethod(fieldElement) 89 .map( 90 methodForAnnotations -> { 91 if (methodForAnnotations == MethodForAnnotations.MISSING) { 92 throw new IllegalStateException( 93 "Method for annotations is missing for " + fieldElement); 94 } 95 return XElements.asMethod(methodForAnnotations.method()); 96 }); 97 } 98 99 private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) { 100 return elementFieldAnnotationMethodMap.computeIfAbsent( 101 fieldElement, this::getAnnotationMethodUncached); 102 } 103 104 private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) { 105 return findProperty(fieldElement) 106 .methodForAnnotationsSignature() 107 .map( 108 signature -> 109 Optional.ofNullable(methodDescriptors().get(signature)) 110 .map(MethodForAnnotations::create) 111 // The method may be missing across different compilations. 112 // See https://youtrack.jetbrains.com/issue/KT-34684 113 .orElse(MethodForAnnotations.MISSING)); 114 } 115 116 /** Gets the getter method of a given field element corresponding to a property. */ 117 Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) { 118 return elementFieldGetterMethodMap.computeIfAbsent( 119 fieldElement, this::getPropertyGetterUncached); 120 } 121 122 private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) { 123 return findProperty(fieldElement) 124 .getterSignature() 125 .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature))); 126 } 127 128 private PropertyMetadata findProperty(XFieldElement field) { 129 String fieldDescriptor = field.getJvmDescriptor(); 130 if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) { 131 return classMetadata().propertiesByFieldSignature().get(fieldDescriptor); 132 } else { 133 // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124 134 final String propertyName = getPropertyNameFromField(field); 135 return classMetadata().propertiesByFieldSignature().values().stream() 136 .filter(property -> propertyName.contentEquals(property.name())) 137 .collect(DaggerCollectors.onlyElement()); 138 } 139 } 140 141 private static String getPropertyNameFromField(XFieldElement field) { 142 String name = XElements.getSimpleName(field); 143 if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) { 144 return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length()); 145 } else { 146 return name; 147 } 148 } 149 150 /** Parse Kotlin class metadata from a given type element. */ 151 static KotlinMetadata from(XTypeElement typeElement) { 152 return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement))); 153 } 154 155 private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) { 156 XAnnotation annotation = typeElement.getAnnotation(ClassNames.KOTLIN_METADATA); 157 Metadata metadataAnnotation = 158 JvmMetadataUtil.Metadata( 159 annotation.getAsInt("k"), 160 annotation.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(), 161 annotation.getAsStringList("d1").toArray(new String[0]), 162 annotation.getAsStringList("d2").toArray(new String[0]), 163 annotation.getAsString("xs"), 164 getOptionalStringValue(annotation, "pn").orElse(null), 165 getOptionalIntValue(annotation, "xi").orElse(null)); 166 KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation); 167 if (metadata == null) { 168 // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e. 169 // kotlinx-metadata-jvm is outdated. 170 throw new IllegalStateException( 171 "Unable to read Kotlin metadata due to unsupported metadata version."); 172 } 173 if (metadata instanceof KotlinClassMetadata.Class) { 174 // TODO(danysantiago): If when we need other types of metadata then move to right method. 175 return (KotlinClassMetadata.Class) metadata; 176 } else { 177 throw new IllegalStateException("Unsupported metadata type: " + metadata); 178 } 179 } 180 181 @AutoValue 182 abstract static class ClassMetadata extends BaseMetadata { 183 abstract Optional<String> companionObjectName(); 184 185 abstract ImmutableSet<FunctionMetadata> constructors(); 186 187 abstract ImmutableMap<String, FunctionMetadata> functionsBySignature(); 188 189 abstract ImmutableMap<String, PropertyMetadata> propertiesByFieldSignature(); 190 191 static ClassMetadata create(KotlinClassMetadata.Class metadata) { 192 KmClass kmClass = metadata.toKmClass(); 193 ClassMetadata.Builder builder = ClassMetadata.builder(kmClass.getFlags(), kmClass.getName()); 194 builder.companionObjectName(Optional.ofNullable(kmClass.getCompanionObject())); 195 kmClass.getConstructors().forEach(it -> builder.addConstructor(FunctionMetadata.create(it))); 196 kmClass.getFunctions().forEach(it -> builder.addFunction(FunctionMetadata.create(it))); 197 kmClass.getProperties().forEach(it -> builder.addProperty(PropertyMetadata.create(it))); 198 return builder.build(); 199 } 200 201 private static Builder builder(int flags, String name) { 202 return new AutoValue_KotlinMetadata_ClassMetadata.Builder().flags(flags).name(name); 203 } 204 205 @AutoValue.Builder 206 abstract static class Builder implements BaseMetadata.Builder<Builder> { 207 abstract Builder companionObjectName(Optional<String> companionObjectName); 208 209 abstract ImmutableSet.Builder<FunctionMetadata> constructorsBuilder(); 210 211 abstract ImmutableMap.Builder<String, FunctionMetadata> functionsBySignatureBuilder(); 212 213 abstract ImmutableMap.Builder<String, PropertyMetadata> propertiesByFieldSignatureBuilder(); 214 215 Builder addConstructor(FunctionMetadata constructor) { 216 constructorsBuilder().add(constructor); 217 functionsBySignatureBuilder().put(constructor.signature(), constructor); 218 return this; 219 } 220 221 Builder addFunction(FunctionMetadata function) { 222 functionsBySignatureBuilder().put(function.signature(), function); 223 return this; 224 } 225 226 Builder addProperty(PropertyMetadata property) { 227 if (property.fieldSignature().isPresent()) { 228 propertiesByFieldSignatureBuilder().put(property.fieldSignature().get(), property); 229 } 230 return this; 231 } 232 233 abstract ClassMetadata build(); 234 } 235 } 236 237 @AutoValue 238 abstract static class FunctionMetadata extends BaseMetadata { 239 abstract String signature(); 240 241 abstract ImmutableList<ValueParameterMetadata> parameters(); 242 243 static FunctionMetadata create(KmConstructor metadata) { 244 FunctionMetadata.Builder builder = FunctionMetadata.builder(metadata.getFlags(), "<init>"); 245 metadata 246 .getValueParameters() 247 .forEach( 248 it -> 249 builder.addParameter(ValueParameterMetadata.create(it.getFlags(), it.getName()))); 250 builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString()); 251 return builder.build(); 252 } 253 254 static FunctionMetadata create(KmFunction metadata) { 255 FunctionMetadata.Builder builder = 256 FunctionMetadata.builder(metadata.getFlags(), metadata.getName()); 257 metadata 258 .getValueParameters() 259 .forEach( 260 it -> 261 builder.addParameter(ValueParameterMetadata.create(it.getFlags(), it.getName()))); 262 builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString()); 263 return builder.build(); 264 } 265 266 private static Builder builder(int flags, String name) { 267 return new AutoValue_KotlinMetadata_FunctionMetadata.Builder().flags(flags).name(name); 268 } 269 270 @AutoValue.Builder 271 abstract static class Builder implements BaseMetadata.Builder<Builder> { 272 abstract Builder signature(String signature); 273 274 abstract ImmutableList.Builder<ValueParameterMetadata> parametersBuilder(); 275 276 Builder addParameter(ValueParameterMetadata parameter) { 277 parametersBuilder().add(parameter); 278 return this; 279 } 280 281 abstract FunctionMetadata build(); 282 } 283 } 284 285 @AutoValue 286 abstract static class PropertyMetadata extends BaseMetadata { 287 /** Returns the JVM field descriptor of the backing field of this property. */ 288 abstract Optional<String> fieldSignature(); 289 290 abstract Optional<String> getterSignature(); 291 292 /** Returns JVM method descriptor of the synthetic method for property annotations. */ 293 abstract Optional<String> methodForAnnotationsSignature(); 294 295 static PropertyMetadata create(KmProperty metadata) { 296 PropertyMetadata.Builder builder = 297 PropertyMetadata.builder(metadata.getFlags(), metadata.getName()); 298 builder.fieldSignature( 299 Optional.ofNullable(JvmExtensionsKt.getFieldSignature(metadata)) 300 .map(JvmFieldSignature::asString)); 301 builder.getterSignature( 302 Optional.ofNullable(JvmExtensionsKt.getGetterSignature(metadata)) 303 .map(JvmMethodSignature::asString)); 304 builder.methodForAnnotationsSignature( 305 Optional.ofNullable(JvmExtensionsKt.getSyntheticMethodForAnnotations(metadata)) 306 .map(JvmMethodSignature::asString)); 307 return builder.build(); 308 } 309 310 private static Builder builder(int flags, String name) { 311 return new AutoValue_KotlinMetadata_PropertyMetadata.Builder().flags(flags).name(name); 312 } 313 314 @AutoValue.Builder 315 interface Builder extends BaseMetadata.Builder<Builder> { 316 Builder fieldSignature(Optional<String> signature); 317 318 Builder getterSignature(Optional<String> signature); 319 320 Builder methodForAnnotationsSignature(Optional<String> signature); 321 322 PropertyMetadata build(); 323 } 324 } 325 326 @AutoValue 327 abstract static class ValueParameterMetadata extends BaseMetadata { 328 private static ValueParameterMetadata create(int flags, String name) { 329 return new AutoValue_KotlinMetadata_ValueParameterMetadata(flags, name); 330 } 331 } 332 333 abstract static class BaseMetadata { 334 /** Returns the Kotlin metadata flags for this property. */ 335 abstract int flags(); 336 337 /** returns {@code true} if the given flag (e.g. {@link Flag.IS_PRIVATE}) applies. */ 338 boolean flags(Flag flag) { 339 return flag.invoke(flags()); 340 } 341 342 /** Returns the simple name of this property. */ 343 abstract String name(); 344 345 interface Builder<BuilderT> { 346 BuilderT flags(int flags); 347 348 BuilderT name(String name); 349 } 350 } 351 352 @AutoValue 353 abstract static class MethodForAnnotations { 354 static MethodForAnnotations create(XMethodElement method) { 355 return new AutoValue_KotlinMetadata_MethodForAnnotations(method); 356 } 357 358 static final MethodForAnnotations MISSING = MethodForAnnotations.create(null); 359 360 @Nullable 361 abstract XMethodElement method(); 362 } 363 364 private static Optional<Integer> getOptionalIntValue(XAnnotation annotation, String valueName) { 365 return isValuePresent(annotation, valueName) 366 ? Optional.of(annotation.getAsInt(valueName)) 367 : Optional.empty(); 368 } 369 370 private static Optional<String> getOptionalStringValue(XAnnotation annotation, String valueName) { 371 return isValuePresent(annotation, valueName) 372 ? Optional.of(annotation.getAsString(valueName)) 373 : Optional.empty(); 374 } 375 376 private static boolean isValuePresent(XAnnotation annotation, String valueName) { 377 return annotation.getAnnotationValues().stream() 378 .anyMatch(member -> member.getName().equals(valueName)); 379 } 380 } 381