1 /* <lambda>null2 * Copyright (C) 2021 Square, Inc. 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 * https://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 package com.squareup.kotlinpoet.metadata.classinspectors 17 18 import com.squareup.kotlinpoet.AnnotationSpec 19 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE 20 import com.squareup.kotlinpoet.ClassName 21 import com.squareup.kotlinpoet.CodeBlock 22 import com.squareup.kotlinpoet.DelicateKotlinPoetApi 23 import com.squareup.kotlinpoet.TypeName 24 import com.squareup.kotlinpoet.asTypeName 25 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED 26 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations 27 import com.squareup.kotlinpoet.metadata.isDeclaration 28 import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata 29 import com.squareup.kotlinpoet.metadata.specs.ClassData 30 import com.squareup.kotlinpoet.metadata.specs.ClassInspector 31 import com.squareup.kotlinpoet.metadata.specs.ConstructorData 32 import com.squareup.kotlinpoet.metadata.specs.ContainerData 33 import com.squareup.kotlinpoet.metadata.specs.EnumEntryData 34 import com.squareup.kotlinpoet.metadata.specs.FieldData 35 import com.squareup.kotlinpoet.metadata.specs.FileData 36 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier 37 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT 38 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE 39 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier 40 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT 41 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC 42 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED 43 import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR 44 import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR 45 import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR 46 import com.squareup.kotlinpoet.metadata.specs.MethodData 47 import com.squareup.kotlinpoet.metadata.specs.PropertyData 48 import com.squareup.kotlinpoet.metadata.toKmClass 49 import java.lang.reflect.Constructor 50 import java.lang.reflect.Field 51 import java.lang.reflect.Method 52 import java.lang.reflect.Modifier 53 import java.lang.reflect.Parameter 54 import java.util.TreeMap 55 import java.util.concurrent.ConcurrentHashMap 56 import kotlin.LazyThreadSafetyMode.NONE 57 import kotlin.metadata.ClassKind 58 import kotlin.metadata.KmClass 59 import kotlin.metadata.KmDeclarationContainer 60 import kotlin.metadata.KmPackage 61 import kotlin.metadata.hasAnnotations 62 import kotlin.metadata.hasConstant 63 import kotlin.metadata.isConst 64 import kotlin.metadata.isValue 65 import kotlin.metadata.jvm.JvmFieldSignature 66 import kotlin.metadata.jvm.JvmMethodSignature 67 import kotlin.metadata.jvm.KotlinClassMetadata 68 import kotlin.metadata.jvm.fieldSignature 69 import kotlin.metadata.jvm.getterSignature 70 import kotlin.metadata.jvm.setterSignature 71 import kotlin.metadata.jvm.signature 72 import kotlin.metadata.jvm.syntheticMethodForAnnotations 73 import kotlin.metadata.kind 74 75 public class ReflectiveClassInspector private constructor( 76 private val lenient: Boolean, 77 private val classLoader: ClassLoader?, 78 ) : ClassInspector { 79 80 private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>() 81 private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>() 82 private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>() 83 private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>() 84 private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>() 85 86 private fun lookupClass(className: ClassName): Class<*>? { 87 return classCache.getOrPut(className) { 88 try { 89 if (classLoader == null) { 90 Class.forName(className.reflectionName()) 91 } else { 92 Class.forName(className.reflectionName(), true, classLoader) 93 } 94 } catch (e: ClassNotFoundException) { 95 null 96 }.toOptional() 97 }.nullableValue 98 } 99 100 override val supportsNonRuntimeRetainedAnnotations: Boolean = false 101 102 override fun declarationContainerFor(className: ClassName): KmDeclarationContainer { 103 val clazz = lookupClass(className) 104 ?: error("No type element found for: $className.") 105 106 val metadata = clazz.getAnnotation(Metadata::class.java) 107 return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata(lenient)) { 108 is KotlinClassMetadata.Class -> kotlinClassMetadata.kmClass 109 is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.kmPackage 110 else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}") 111 } 112 } 113 114 override fun isInterface(className: ClassName): Boolean { 115 if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) { 116 return true 117 } 118 return lookupClass(className)?.isInterface ?: false 119 } 120 121 private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? { 122 return try { 123 val signatureString = fieldSignature.toString() 124 fieldCache.getOrPut(this to signatureString) { 125 declaredFields 126 .asSequence() 127 .onEach { it.isAccessible = true } 128 .find { signatureString == it.jvmFieldSignature }.toOptional() 129 }.nullableValue 130 } catch (e: ClassNotFoundException) { 131 null 132 } 133 } 134 135 private fun Class<*>.lookupMethod( 136 methodSignature: JvmMethodSignature, 137 ): Method? { 138 val signatureString = methodSignature.toString() 139 return methodCache.getOrPut(this to signatureString) { 140 declaredMethods 141 .asSequence() 142 .onEach { it.isAccessible = true } 143 .find { signatureString == it.jvmMethodSignature }.toOptional() 144 }.nullableValue 145 } 146 147 private fun Class<*>.lookupConstructor( 148 constructorSignature: JvmMethodSignature, 149 ): Constructor<*>? { 150 val signatureString = constructorSignature.toString() 151 return constructorCache.getOrPut(this to signatureString) { 152 declaredConstructors 153 .asSequence() 154 .onEach { it.isAccessible = true } 155 .find { signatureString == it.jvmMethodSignature }.toOptional() 156 }.nullableValue 157 } 158 159 private fun Field.jvmModifiers(): Set<JvmFieldModifier> { 160 return mutableSetOf<JvmFieldModifier>().apply { 161 if (Modifier.isTransient(modifiers)) { 162 add(TRANSIENT) 163 } 164 if (Modifier.isVolatile(modifiers)) { 165 add(VOLATILE) 166 } 167 if (Modifier.isStatic(modifiers)) { 168 add(JvmFieldModifier.STATIC) 169 } 170 } 171 } 172 173 @OptIn(DelicateKotlinPoetApi::class) 174 private fun Field.annotationSpecs(): List<AnnotationSpec> { 175 return filterOutNullabilityAnnotations( 176 declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) }, 177 ) 178 } 179 180 @OptIn(DelicateKotlinPoetApi::class) 181 private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> { 182 return filterOutNullabilityAnnotations( 183 declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) }, 184 ) 185 } 186 187 private fun Method.jvmModifiers(): Set<JvmMethodModifier> { 188 return methodJvmModifiers(modifiers, isDefault) 189 } 190 191 private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> { 192 return methodJvmModifiers(modifiers, false) 193 } 194 195 private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> { 196 val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>() 197 if (Modifier.isSynchronized(modifiers)) { 198 jvmMethodModifiers += SYNCHRONIZED 199 } 200 if (Modifier.isStatic(modifiers)) { 201 jvmMethodModifiers += STATIC 202 } 203 if (isDefault) { 204 jvmMethodModifiers += DEFAULT 205 } 206 return jvmMethodModifiers 207 } 208 209 @OptIn(DelicateKotlinPoetApi::class) 210 private fun Method.annotationSpecs(): List<AnnotationSpec> { 211 return filterOutNullabilityAnnotations( 212 declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) }, 213 ) 214 } 215 216 @OptIn(DelicateKotlinPoetApi::class) 217 private fun Parameter.annotationSpecs(): List<AnnotationSpec> { 218 return filterOutNullabilityAnnotations( 219 declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) }, 220 ) 221 } 222 223 private fun Method.exceptionTypeNames(): List<TypeName> { 224 return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() } 225 } 226 227 private fun Constructor<*>.exceptionTypeNames(): List<TypeName> { 228 return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() } 229 } 230 231 override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData { 232 val clazz = lookupClass(enumClassName) 233 ?: error("No class found for: $enumClassName.") 234 check(clazz.isEnum) { 235 "Class must be an enum but isn't: $clazz" 236 } 237 val enumEntry = enumCache.getOrPut(clazz to memberName) { 238 clazz.enumConstants 239 .asSequence() 240 .map { it as Enum<*> } 241 .find { it.name == memberName } 242 .toOptional() 243 }.nullableValue 244 checkNotNull(enumEntry) { 245 "Could not find $memberName on $enumClassName" 246 } 247 return EnumEntryData( 248 declarationContainer = if (enumEntry.javaClass == clazz) { 249 // For simple enums with no class bodies, the entry class will be the same as the original 250 // class. 251 null 252 } else { 253 enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass(lenient) 254 }, 255 annotations = clazz.getField(enumEntry.name).annotationSpecs(), 256 ) 257 } 258 259 private fun Field.constantValue(): CodeBlock? { 260 if (!Modifier.isStatic(modifiers)) { 261 return null 262 } 263 return get(null) // Constant means we can do a static get on it. 264 .let(ClassInspectorUtil::codeLiteralOf) 265 } 266 267 private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean { 268 val signatureString = toString() 269 val classPackage = clazz.`package`.name 270 val interfaceMethods = clazz.interfaces.asSequence() 271 .flatMap { it.methods.asSequence() } 272 val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence() 273 return interfaceMethods.plus(superClassMethods) 274 .filterNot { Modifier.isFinal(it.modifiers) } 275 .filterNot { Modifier.isStatic(it.modifiers) } 276 .filterNot { Modifier.isPrivate(it.modifiers) } 277 .filter { 278 Modifier.isPublic(it.modifiers) || 279 Modifier.isProtected(it.modifiers) || 280 // Package private 281 it.declaringClass.`package`.name == classPackage 282 } 283 .map { it.jvmMethodSignature } 284 .any { it == signatureString } 285 } 286 287 override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean { 288 return lookupClass(className)?.lookupMethod(methodSignature) != null 289 } 290 291 @OptIn(DelicateKotlinPoetApi::class) 292 override fun containerData( 293 declarationContainer: KmDeclarationContainer, 294 className: ClassName, 295 parentClassName: ClassName?, 296 ): ContainerData { 297 val targetClass = lookupClass(className) ?: error("No class found for: $className.") 298 val isCompanionObject: Boolean = when (declarationContainer) { 299 is KmClass -> { 300 declarationContainer.kind == ClassKind.COMPANION_OBJECT 301 } 302 is KmPackage -> { 303 false 304 } 305 else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}") 306 } 307 308 // Should only be called if parentName has been null-checked 309 val classIfCompanion by lazy(NONE) { 310 if (isCompanionObject && parentClassName != null) { 311 lookupClass(parentClassName) 312 ?: error("No class found for: $parentClassName.") 313 } else { 314 targetClass 315 } 316 } 317 318 val propertyData = declarationContainer.properties 319 .asSequence() 320 .filter { it.kind.isDeclaration } 321 .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property -> 322 val isJvmField = ClassInspectorUtil.computeIsJvmField( 323 property = property, 324 classInspector = this, 325 isCompanionObject = isCompanionObject, 326 hasGetter = property.getterSignature != null, 327 hasSetter = property.setterSignature != null, 328 hasField = property.fieldSignature != null, 329 ) 330 331 val fieldData = property.fieldSignature?.let { fieldSignature -> 332 // Check the field in the parent first. For const/static/jvmField elements, these only 333 // exist in the parent and we want to check that if necessary to avoid looking up a 334 // non-existent field in the companion. 335 val parentModifiers = if (isCompanionObject && parentClassName != null) { 336 classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty() 337 } else { 338 emptySet() 339 } 340 341 val isStatic = JvmFieldModifier.STATIC in parentModifiers 342 343 // TODO we looked up field once, let's reuse it 344 val classForOriginalField = targetClass.takeUnless { 345 isCompanionObject && 346 (property.isConst || isJvmField || isStatic) 347 } ?: classIfCompanion 348 349 val field = classForOriginalField.lookupField(fieldSignature) 350 ?: error("No field $fieldSignature found in $classForOriginalField.") 351 val constant = if (property.hasConstant) { 352 val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let { 353 if (it.isInterface) { 354 field 355 } else { 356 // const properties are relocated to the enclosing class 357 it.lookupField(fieldSignature) 358 ?: error("No field $fieldSignature found in $it.") 359 } 360 } ?: field 361 fieldWithConstant.constantValue() 362 } else { 363 null 364 } 365 366 val jvmModifiers = field.jvmModifiers() + parentModifiers 367 368 // For static, const, or JvmField fields in a companion object, the companion 369 // object's field is marked as synthetic to hide it from Java, but in this case 370 // it's a false positive for this check in kotlin. 371 val isSynthetic = field.isSynthetic && 372 !( 373 isCompanionObject && 374 (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers) 375 ) 376 377 FieldData( 378 annotations = field.annotationSpecs(), 379 isSynthetic = isSynthetic, 380 jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) { 381 // JvmField companion objects don't need JvmStatic, it's implicit 382 isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC 383 }, 384 constant = constant, 385 ) 386 } 387 388 val getterData = property.getterSignature?.let { getterSignature -> 389 val method = classIfCompanion.lookupMethod(getterSignature) 390 method?.methodData( 391 clazz = targetClass, 392 signature = getterSignature, 393 hasAnnotations = property.getter.hasAnnotations, 394 jvmInformationMethod = classIfCompanion.takeIf { it != targetClass } 395 ?.lookupMethod(getterSignature) ?: method, 396 ) 397 ?: error("No getter method $getterSignature found in $classIfCompanion.") 398 } 399 400 val setterData = property.setterSignature?.let { setterSignature -> 401 val method = classIfCompanion.lookupMethod(setterSignature) 402 method?.methodData( 403 clazz = targetClass, 404 signature = setterSignature, 405 hasAnnotations = property.setter?.hasAnnotations ?: false, 406 jvmInformationMethod = classIfCompanion.takeIf { it != targetClass } 407 ?.lookupMethod(setterSignature) ?: method, 408 knownIsOverride = getterData?.isOverride, 409 ) 410 ?: error("No setter method $setterSignature found in $classIfCompanion.") 411 } 412 413 val annotations = mutableListOf<AnnotationSpec>() 414 if (property.hasAnnotations) { 415 property.syntheticMethodForAnnotations?.let { annotationsHolderSignature -> 416 targetClass.lookupMethod(annotationsHolderSignature)?.let { method -> 417 annotations += method.annotationSpecs() 418 // Cover for https://github.com/square/kotlinpoet/issues/1046 419 .filterNot { it.typeName == JAVA_DEPRECATED } 420 } 421 } 422 } 423 424 PropertyData( 425 annotations = annotations, 426 fieldData = fieldData, 427 getterData = getterData, 428 setterData = setterData, 429 isJvmField = isJvmField, 430 ) 431 } 432 433 val methodData = declarationContainer.functions 434 .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction -> 435 val signature = kmFunction.signature 436 if (signature != null) { 437 val method = targetClass.lookupMethod(signature) 438 method?.methodData( 439 clazz = targetClass, 440 signature = signature, 441 hasAnnotations = kmFunction.hasAnnotations, 442 jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature) 443 ?: method, 444 ) 445 ?: error("No method $signature found in $targetClass.") 446 } else { 447 MethodData.EMPTY 448 } 449 } 450 451 when (declarationContainer) { 452 is KmClass -> { 453 val classAnnotations = if (declarationContainer.hasAnnotations) { 454 ClassInspectorUtil.createAnnotations { 455 addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) }) 456 } 457 } else { 458 emptyList() 459 } 460 val constructorData = declarationContainer.constructors 461 .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor -> 462 if (declarationContainer.kind == ClassKind.ANNOTATION_CLASS || declarationContainer.isValue) { 463 // 464 // Annotations are interfaces in reflection, but kotlin metadata will still report a 465 // constructor signature 466 // 467 // Inline classes have no constructors at runtime 468 // 469 return@associateWithTo ConstructorData.EMPTY 470 } 471 val signature = kmConstructor.signature 472 if (signature != null) { 473 val constructor = targetClass.lookupConstructor(signature) 474 ?: error("No constructor $signature found in $targetClass.") 475 ConstructorData( 476 annotations = if (kmConstructor.hasAnnotations) { 477 constructor.annotationSpecs() 478 } else { 479 emptyList() 480 }, 481 parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(), 482 isSynthetic = constructor.isSynthetic, 483 jvmModifiers = constructor.jvmModifiers(), 484 exceptions = constructor.exceptionTypeNames(), 485 ) 486 } else { 487 ConstructorData.EMPTY 488 } 489 } 490 return ClassData( 491 declarationContainer = declarationContainer, 492 className = className, 493 annotations = classAnnotations, 494 properties = propertyData, 495 constructors = constructorData, 496 methods = methodData, 497 ) 498 } 499 is KmPackage -> { 500 // There's no flag for checking if there are annotations, so we just eagerly check in this 501 // case. All annotations on this class are file: site targets in source. This does not 502 // include @JvmName since it does not have RUNTIME retention. In practice this doesn't 503 // really matter, but it does mean we can't know for certain if the file should be called 504 // FooKt.kt or Foo.kt. 505 val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) { 506 addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) }) 507 } 508 return FileData( 509 declarationContainer = declarationContainer, 510 annotations = fileAnnotations, 511 properties = propertyData, 512 methods = methodData, 513 className = className, 514 ) 515 } 516 else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}") 517 } 518 } 519 520 private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> { 521 return withIndex().associate { (index, parameter) -> 522 index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) } 523 } 524 } 525 526 private fun Method.methodData( 527 clazz: Class<*>, 528 signature: JvmMethodSignature, 529 hasAnnotations: Boolean, 530 jvmInformationMethod: Method = this, 531 knownIsOverride: Boolean? = null, 532 ): MethodData { 533 return MethodData( 534 annotations = if (hasAnnotations) annotationSpecs() else emptyList(), 535 parameterAnnotations = parameters.indexedAnnotationSpecs(), 536 isSynthetic = isSynthetic, 537 jvmModifiers = jvmInformationMethod.jvmModifiers(), 538 isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz), 539 exceptions = exceptionTypeNames(), 540 ) 541 } 542 543 public companion object { 544 /** 545 * @param lenient see docs on [KotlinClassMetadata.readStrict] and [KotlinClassMetadata.readLenient] for more details. 546 */ 547 @JvmStatic 548 public fun create(lenient: Boolean, classLoader: ClassLoader? = null): ClassInspector { 549 return ReflectiveClassInspector(lenient, classLoader) 550 } 551 552 private val Class<*>.descriptor: String 553 get() { 554 return when { 555 isPrimitive -> when (kotlin) { 556 Byte::class -> "B" 557 Char::class -> "C" 558 Double::class -> "D" 559 Float::class -> "F" 560 Int::class -> "I" 561 Long::class -> "J" 562 Short::class -> "S" 563 Boolean::class -> "Z" 564 Void::class -> "V" 565 else -> throw RuntimeException("Unrecognized primitive $this") 566 } 567 isArray -> name.replace('.', '/') 568 else -> "L$name;".replace('.', '/') 569 } 570 } 571 572 private val Method.descriptor: String 573 get() = parameterTypes.joinToString( 574 separator = "", 575 prefix = "(", 576 postfix = ")${returnType.descriptor}", 577 ) { it.descriptor } 578 579 /** 580 * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`. 581 * 582 * Useful for comparing with [JvmMethodSignature]. 583 * 584 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3). 585 */ 586 private val Method.jvmMethodSignature: String get() = "$name$descriptor" 587 588 private val Constructor<*>.descriptor: String 589 get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor } 590 591 /** 592 * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`. 593 * 594 * Useful for comparing with [JvmMethodSignature]. 595 * 596 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3). 597 */ 598 private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor" 599 600 /** 601 * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`. 602 * 603 * Useful for comparing with [JvmFieldSignature]. 604 * 605 * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3). 606 */ 607 private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}" 608 } 609 } 610