blob: a8d51b38c2962a84a0f5c382e9aafed3d757fc75 [file]
/*
* Copyright 2022 Google LLC
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.ksp.symbol.impl
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.impl.KSObjectCache
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.Modifier
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtDeclarationWithInitializer
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.FieldVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
fun MemberDescriptor.toKSModifiers(): Set<Modifier> {
val modifiers = mutableSetOf<Modifier>()
if (this.isActual) {
modifiers.add(Modifier.ACTUAL)
}
if (this.isExpect) {
modifiers.add(Modifier.EXPECT)
}
if (this.isExternal) {
modifiers.add(Modifier.EXTERNAL)
}
// we are not checking for JVM_STATIC annotation here intentionally
// see: https://github.com/google/ksp/issues/378
val isStatic = (this.containingDeclaration as? ClassDescriptor)?.let { containingClass ->
containingClass.staticScope.getContributedDescriptors(
nameFilter = {
it == this.name
}
).any {
it == this
}
} ?: false
if (isStatic) {
modifiers.add(Modifier.JAVA_STATIC)
}
when (this.modality) {
Modality.SEALED -> modifiers.add(Modifier.SEALED)
Modality.FINAL -> modifiers.add(Modifier.FINAL)
Modality.OPEN -> {
if (!isStatic && this.visibility != DescriptorVisibilities.PRIVATE) {
// private methods still show up as OPEN
modifiers.add(Modifier.OPEN)
}
}
Modality.ABSTRACT -> modifiers.add(Modifier.ABSTRACT)
}
when (this.visibility) {
DescriptorVisibilities.PUBLIC -> modifiers.add(Modifier.PUBLIC)
DescriptorVisibilities.PROTECTED,
JavaDescriptorVisibilities.PROTECTED_AND_PACKAGE,
JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY,
-> modifiers.add(Modifier.PROTECTED)
DescriptorVisibilities.PRIVATE,
DescriptorVisibilities.PRIVATE_TO_THIS,
DescriptorVisibilities.LOCAL -> modifiers.add(Modifier.PRIVATE)
DescriptorVisibilities.INTERNAL -> modifiers.add(Modifier.INTERNAL)
// Since there is no modifier for package-private, use No modifier to tell if a symbol from binary is package private.
JavaDescriptorVisibilities.PACKAGE_VISIBILITY, JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY -> Unit
else -> throw IllegalStateException("unhandled visibility: ${this.visibility}")
}
return modifiers
}
fun FunctionDescriptor.toFunctionKSModifiers(): Set<Modifier> {
val modifiers = mutableSetOf<Modifier>()
if (this.isSuspend) {
modifiers.add(Modifier.SUSPEND)
}
if (this.isTailrec) {
modifiers.add(Modifier.TAILREC)
}
if (this.isInline) {
modifiers.add(Modifier.INLINE)
}
if (this.isInfix) {
modifiers.add(Modifier.INFIX)
}
if (this.isOperator) {
modifiers.add(Modifier.OPERATOR)
}
if (this.overriddenDescriptors.isNotEmpty()) {
modifiers.add(Modifier.OVERRIDE)
}
return modifiers
}
/**
* Custom check for backing fields of descriptors that support properties coming from .class files.
* The compiler API always returns true for them even when they don't have backing fields.
*/
fun PropertyDescriptor.hasBackingFieldWithBinaryClassSupport(): Boolean {
// partially take from https://github.com/JetBrains/kotlin/blob/master/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightMembersCreator.kt#L104
return when {
extensionReceiverParameter != null -> false // extension properties do not have backing fields
compileTimeInitializer != null -> true // compile time initialization requires backing field
isLateInit -> true // lateinit requires property, faster than parsing class declaration
modality == Modality.ABSTRACT -> false // abstract means false, faster than parsing class declaration
this is DeserializedPropertyDescriptor -> this.hasBackingFieldInBinaryClass() // kotlin class, check binary
this.source is KotlinSourceElement -> this.declaresDefaultValue // kotlin source
else -> true // Java source or class
}
}
data class BinaryClassInfo(
val fieldAccFlags: Map<String, Int>,
val methodAccFlags: Map<String, Int>
)
/**
* Lookup cache for field names names for deserialized classes.
* To check if a field has backing field, we need to look for binary field names, hence they are cached here.
*/
object BinaryClassInfoCache : KSObjectCache<ClassId, BinaryClassInfo>() {
fun getCached(
kotlinJvmBinaryClass: KotlinJvmBinaryClass,
) = getCached(
kotlinJvmBinaryClass.classId, (kotlinJvmBinaryClass as? VirtualFileKotlinClass)?.file?.contentsToByteArray()
)
fun getCached(classId: ClassId, virtualFileContent: ByteArray?) = cache.getOrPut(classId) {
val fieldAccFlags = mutableMapOf<String, Int>()
val methodAccFlags = mutableMapOf<String, Int>()
ClassReader(virtualFileContent).accept(
object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor? {
if (name != null) {
fieldAccFlags.put(name, access)
}
return null
}
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
if (name != null) {
methodAccFlags.put(name + descriptor, access)
}
return null
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
BinaryClassInfo(fieldAccFlags, methodAccFlags)
}
}
/**
* Workaround for backingField in deserialized descriptors.
* They always return non-null for backing field even when they don't have a backing field.
*/
private fun DeserializedPropertyDescriptor.hasBackingFieldInBinaryClass(): Boolean {
val kotlinJvmBinaryClass = if (containingDeclaration.isCompanionObject()) {
// Companion objects have backing fields in containing classes.
// https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-fields
val container = containingDeclaration.containingDeclaration as? DeserializedClassDescriptor
(container?.source as? KotlinJvmBinarySourceElement)?.binaryClass
} else {
this.getContainingKotlinJvmBinaryClass()
} ?: return false
return BinaryClassInfoCache.getCached(kotlinJvmBinaryClass).fieldAccFlags.containsKey(name.asString())
}
// from: https://github.com/JetBrains/kotlin/blob/92d200e093c693b3c06e53a39e0b0973b84c7ec5/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt#L45
private val PropertyDescriptor.declaresDefaultValue: Boolean
get() = when (val declaration = this.source.getPsi()) {
is KtDeclarationWithInitializer -> declaration.initializer != null
is KtParameter -> declaration.defaultValue != null
else -> false
}
fun KSAnnotated.hasAnnotation(fqn: String): Boolean =
annotations.any {
fqn.endsWith(it.shortName.asString()) &&
it.annotationType.resolve().declaration.qualifiedName?.asString() == fqn
}
fun Resolver.extractThrowsFromClassFile(
virtualFileContent: ByteArray,
jvmDesc: String?,
simpleName: String?
): Sequence<KSType> {
val exceptionNames = mutableListOf<String>()
ClassReader(virtualFileContent).accept(
object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?,
): MethodVisitor {
if (name == simpleName && jvmDesc == descriptor) {
exceptions?.toList()?.let { exceptionNames.addAll(it) }
}
return object : MethodVisitor(Opcodes.API_VERSION) {
}
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
return exceptionNames.mapNotNull {
this.getClassDeclarationByName(it.replace("/", "."))?.asStarProjectedType()
}.asSequence()
}