• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 The Android Open Source Project
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 
17 package com.google.accompanist.permissions.lint.util
18 
19 import com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue
20 import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
21 import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
22 import com.intellij.psi.PsiAnnotation
23 import com.intellij.psi.PsiClass
24 import com.intellij.psi.PsiMethod
25 import com.intellij.psi.impl.compiled.ClsMethodImpl
26 import com.intellij.psi.util.ClassUtil
27 import kotlinx.metadata.KmDeclarationContainer
28 import kotlinx.metadata.KmFunction
29 import kotlinx.metadata.jvm.KotlinClassHeader
30 import kotlinx.metadata.jvm.KotlinClassMetadata
31 import kotlinx.metadata.jvm.signature
32 
33 // FILE COPIED FROM:
34 // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
35 
36 /**
37  * @return the corresponding [KmFunction] for this [PsiMethod], or `null` if there is no
38  * corresponding [KmFunction]. This method is only meaningful if this [PsiMethod] represents a
39  * method defined in bytecode (most often a [ClsMethodImpl]).
40  */
toKmFunctionnull41 public fun PsiMethod.toKmFunction(): KmFunction? =
42     containingClass!!.getKmDeclarationContainer()?.findKmFunctionForPsiMethod(this)
43 
44 // TODO: https://youtrack.jetbrains.com/issue/KT-45310
45 // Currently there is no built in support for parsing kotlin metadata from kotlin class files, so
46 // we need to manually inspect the annotations and work with Cls* (compiled PSI).
47 /**
48  * Returns the [KmDeclarationContainer] using the kotlin.Metadata annotation present on this
49  * [PsiClass]. Returns null if there is no annotation (not parsing a Kotlin
50  * class file), the annotation data is for an unsupported version of Kotlin, or if the metadata
51  * represents a synthetic class.
52  */
53 private fun PsiClass.getKmDeclarationContainer(): KmDeclarationContainer? {
54     val classKotlinMetadataAnnotation = annotations.find {
55         // hasQualifiedName() not available on the min version of Lint we compile against
56         it.qualifiedName == KotlinMetadataFqn
57     } ?: return null
58 
59     val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
60         ?: return null
61 
62     return when (metadata) {
63         is KotlinClassMetadata.Class -> metadata.toKmClass()
64         is KotlinClassMetadata.FileFacade -> metadata.toKmPackage()
65         is KotlinClassMetadata.SyntheticClass -> null
66         is KotlinClassMetadata.MultiFileClassFacade -> null
67         is KotlinClassMetadata.MultiFileClassPart -> metadata.toKmPackage()
68         is KotlinClassMetadata.Unknown -> null
69     }
70 }
71 
72 /**
73  * Returns a [KotlinClassHeader] by parsing the attributes of this @kotlin.Metadata annotation.
74  *
75  * See: https://github.com/udalov/kotlinx-metadata-examples/blob/master/src/main/java
76  * /examples/FindKotlinGeneratedMethods.java
77  */
PsiAnnotationnull78 private fun PsiAnnotation.toHeader(): KotlinClassHeader {
79     val attributes = attributes.associate { it.attributeName to it.attributeValue }
80 
81     fun JvmAnnotationAttributeValue.parseString(): String =
82         (this as JvmAnnotationConstantValue).constantValue as String
83 
84     fun JvmAnnotationAttributeValue.parseInt(): Int =
85         (this as JvmAnnotationConstantValue).constantValue as Int
86 
87     fun JvmAnnotationAttributeValue.parseStringArray(): Array<String> =
88         (this as JvmAnnotationArrayValue).values.map {
89             it.parseString()
90         }.toTypedArray()
91 
92     fun JvmAnnotationAttributeValue.parseIntArray(): IntArray =
93         (this as JvmAnnotationArrayValue).values.map {
94             it.parseInt()
95         }.toTypedArray().toIntArray()
96 
97     val kind = attributes["k"]?.parseInt()
98     val metadataVersion = attributes["mv"]?.parseIntArray()
99     val data1 = attributes["d1"]?.parseStringArray()
100     val data2 = attributes["d2"]?.parseStringArray()
101     val extraString = attributes["xs"]?.parseString()
102     val packageName = attributes["pn"]?.parseString()
103     val extraInt = attributes["xi"]?.parseInt()
104 
105     return KotlinClassHeader(
106         kind,
107         metadataVersion,
108         data1,
109         data2,
110         extraString,
111         packageName,
112         extraInt
113     )
114 }
115 
116 /**
117  * @return the corresponding [KmFunction] in [this] for the given [method], matching by name and
118  * signature.
119  */
findKmFunctionForPsiMethodnull120 private fun KmDeclarationContainer.findKmFunctionForPsiMethod(method: PsiMethod): KmFunction? {
121     // Strip any mangled part of the name in case of inline classes
122     val expectedName = method.name.substringBefore("-")
123     val expectedSignature = ClassUtil.getAsmMethodSignature(method)
124 
125     return functions.find {
126         it.name == expectedName && it.signature?.desc == expectedSignature
127     }
128 }
129 
130 private const val KotlinMetadataFqn = "kotlin.Metadata"
131