xref: /aosp_15_r20/external/ksp/common-util/src/main/kotlin/com/google/devtools/ksp/DescriptorUtils.kt (revision af87fb4bb8e3042070d2a054e912924f599b22b7)
1 /*
<lambda>null2  * Copyright 2022 Google LLC
3  * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.google.devtools.ksp
19 
20 import com.google.devtools.ksp.symbol.KSAnnotated
21 import com.google.devtools.ksp.symbol.Modifier
22 import com.google.devtools.ksp.symbol.Variance
23 import org.jetbrains.kotlin.descriptors.ClassDescriptor
24 import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
25 import org.jetbrains.kotlin.descriptors.FunctionDescriptor
26 import org.jetbrains.kotlin.descriptors.MemberDescriptor
27 import org.jetbrains.kotlin.descriptors.Modality
28 import org.jetbrains.kotlin.descriptors.PropertyDescriptor
29 import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
30 import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
31 import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
32 import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
33 import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
34 import org.jetbrains.kotlin.name.ClassId
35 import org.jetbrains.kotlin.psi.KtDeclarationWithInitializer
36 import org.jetbrains.kotlin.psi.KtParameter
37 import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
38 import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
39 import org.jetbrains.kotlin.resolve.source.getPsi
40 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
41 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
42 import org.jetbrains.org.objectweb.asm.ClassReader
43 import org.jetbrains.org.objectweb.asm.ClassVisitor
44 import org.jetbrains.org.objectweb.asm.FieldVisitor
45 import org.jetbrains.org.objectweb.asm.MethodVisitor
46 import org.jetbrains.org.objectweb.asm.Opcodes
47 
48 fun MemberDescriptor.toKSModifiers(): Set<Modifier> {
49     val modifiers = mutableSetOf<Modifier>()
50     if (this.isActual) {
51         modifiers.add(Modifier.ACTUAL)
52     }
53     if (this.isExpect) {
54         modifiers.add(Modifier.EXPECT)
55     }
56     if (this.isExternal) {
57         modifiers.add(Modifier.EXTERNAL)
58     }
59     // we are not checking for JVM_STATIC annotation here intentionally
60     // see: https://github.com/google/ksp/issues/378
61     val isStatic = (this.containingDeclaration as? ClassDescriptor)?.let { containingClass ->
62         containingClass.staticScope.getContributedDescriptors(
63             nameFilter = {
64                 it == this.name
65             }
66         ).any {
67             it == this
68         }
69     } ?: false
70     if (isStatic) {
71         modifiers.add(Modifier.JAVA_STATIC)
72     }
73     when (this.modality) {
74         Modality.SEALED -> modifiers.add(Modifier.SEALED)
75         Modality.FINAL -> modifiers.add(Modifier.FINAL)
76         Modality.OPEN -> {
77             if (!isStatic && this.visibility != DescriptorVisibilities.PRIVATE) {
78                 // private methods still show up as OPEN
79                 modifiers.add(Modifier.OPEN)
80             }
81         }
82         Modality.ABSTRACT -> modifiers.add(Modifier.ABSTRACT)
83     }
84     when (this.visibility) {
85         DescriptorVisibilities.PUBLIC -> modifiers.add(Modifier.PUBLIC)
86         DescriptorVisibilities.PROTECTED,
87         JavaDescriptorVisibilities.PROTECTED_AND_PACKAGE,
88         JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY,
89         -> modifiers.add(Modifier.PROTECTED)
90         DescriptorVisibilities.PRIVATE, DescriptorVisibilities.LOCAL -> modifiers.add(Modifier.PRIVATE)
91         DescriptorVisibilities.INTERNAL -> modifiers.add(Modifier.INTERNAL)
92         // Since there is no modifier for package-private, use No modifier to tell if a symbol from binary is package private.
93         JavaDescriptorVisibilities.PACKAGE_VISIBILITY, JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY -> Unit
94         else -> throw IllegalStateException("unhandled visibility: ${this.visibility}")
95     }
96 
97     return modifiers
98 }
99 
FunctionDescriptornull100 fun FunctionDescriptor.toFunctionKSModifiers(): Set<Modifier> {
101     val modifiers = mutableSetOf<Modifier>()
102     if (this.isSuspend) {
103         modifiers.add(Modifier.SUSPEND)
104     }
105     if (this.isTailrec) {
106         modifiers.add(Modifier.TAILREC)
107     }
108     if (this.isInline) {
109         modifiers.add(Modifier.INLINE)
110     }
111     if (this.isInfix) {
112         modifiers.add(Modifier.INFIX)
113     }
114     if (this.isOperator) {
115         modifiers.add(Modifier.OPERATOR)
116     }
117     if (this.overriddenDescriptors.isNotEmpty()) {
118         modifiers.add(Modifier.OVERRIDE)
119     }
120 
121     return modifiers
122 }
123 
orgnull124 fun org.jetbrains.kotlin.types.Variance.toKSVariance(): Variance {
125     return when (this) {
126         org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Variance.CONTRAVARIANT
127         org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Variance.COVARIANT
128         org.jetbrains.kotlin.types.Variance.INVARIANT -> Variance.INVARIANT
129         else -> throw IllegalStateException("Unexpected variance value $this, $ExceptionMessage")
130     }
131 }
132 
133 /**
134  * Custom check for backing fields of descriptors that support properties coming from .class files.
135  * The compiler API always returns true for them even when they don't have backing fields.
136  */
PropertyDescriptornull137 fun PropertyDescriptor.hasBackingFieldWithBinaryClassSupport(): Boolean {
138     // partially take from https://github.com/JetBrains/kotlin/blob/master/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightMembersCreator.kt#L104
139     return when {
140         extensionReceiverParameter != null -> false // extension properties do not have backing fields
141         compileTimeInitializer != null -> true // compile time initialization requires backing field
142         isLateInit -> true // lateinit requires property, faster than parsing class declaration
143         modality == Modality.ABSTRACT -> false // abstract means false, faster than parsing class declaration
144         this is DeserializedPropertyDescriptor -> this.hasBackingFieldInBinaryClass() // kotlin class, check binary
145         this.source is KotlinSourceElement -> this.declaresDefaultValue // kotlin source
146         else -> true // Java source or class
147     }
148 }
149 
150 data class BinaryClassInfo(
151     val fieldAccFlags: Map<String, Int>,
152     val methodAccFlags: Map<String, Int>
153 )
154 
155 /**
156  * Lookup cache for field names names for deserialized classes.
157  * To check if a field has backing field, we need to look for binary field names, hence they are cached here.
158  */
159 object BinaryClassInfoCache : KSObjectCache<ClassId, BinaryClassInfo>() {
getCachednull160     fun getCached(
161         kotlinJvmBinaryClass: KotlinJvmBinaryClass,
162     ) = cache.getOrPut(kotlinJvmBinaryClass.classId) {
163         val virtualFileContent = (kotlinJvmBinaryClass as? VirtualFileKotlinClass)?.file?.contentsToByteArray()
164         val fieldAccFlags = mutableMapOf<String, Int>()
165         val methodAccFlags = mutableMapOf<String, Int>()
166         ClassReader(virtualFileContent).accept(
167             object : ClassVisitor(Opcodes.API_VERSION) {
168                 override fun visitField(
169                     access: Int,
170                     name: String?,
171                     descriptor: String?,
172                     signature: String?,
173                     value: Any?
174                 ): FieldVisitor? {
175                     if (name != null) {
176                         fieldAccFlags.put(name, access)
177                     }
178                     return null
179                 }
180 
181                 override fun visitMethod(
182                     access: Int,
183                     name: String?,
184                     descriptor: String?,
185                     signature: String?,
186                     exceptions: Array<out String>?
187                 ): MethodVisitor? {
188                     if (name != null) {
189                         methodAccFlags.put(name + descriptor, access)
190                     }
191                     return null
192                 }
193             },
194             ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
195         )
196         BinaryClassInfo(fieldAccFlags, methodAccFlags)
197     }
198 }
199 
200 /**
201  * Workaround for backingField in deserialized descriptors.
202  * They always return non-null for backing field even when they don't have a backing field.
203  */
hasBackingFieldInBinaryClassnull204 private fun DeserializedPropertyDescriptor.hasBackingFieldInBinaryClass(): Boolean {
205     val kotlinJvmBinaryClass = if (containingDeclaration.isCompanionObject()) {
206         // Companion objects have backing fields in containing classes.
207         // https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-fields
208         val container = containingDeclaration.containingDeclaration as? DeserializedClassDescriptor
209         (container?.source as? KotlinJvmBinarySourceElement)?.binaryClass
210     } else {
211         this.getContainingKotlinJvmBinaryClass()
212     } ?: return false
213     return BinaryClassInfoCache.getCached(kotlinJvmBinaryClass).fieldAccFlags.containsKey(name.asString())
214 }
215 
216 // from: https://github.com/JetBrains/kotlin/blob/92d200e093c693b3c06e53a39e0b0973b84c7ec5/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt#L45
217 private val PropertyDescriptor.declaresDefaultValue: Boolean
218     get() = when (val declaration = this.source.getPsi()) {
219         is KtDeclarationWithInitializer -> declaration.initializer != null
220         is KtParameter -> declaration.defaultValue != null
221         else -> false
222     }
223 
hasAnnotationnull224 fun KSAnnotated.hasAnnotation(fqn: String): Boolean =
225     annotations.any {
226         fqn.endsWith(it.shortName.asString()) &&
227             it.annotationType.resolve().declaration.qualifiedName?.asString() == fqn
228     }
229