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