• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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  *      http://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.android.tools.metalava.model.psi
18 
19 import com.android.tools.metalava.model.ANNOTATION_ATTR_VALUE
20 import com.android.tools.metalava.model.AnnotationItem
21 import com.android.tools.metalava.model.CallableBody
22 import com.android.tools.metalava.model.CallableItem
23 import com.android.tools.metalava.model.ClassItem
24 import com.android.tools.metalava.reporter.FileLocation
25 import com.android.tools.metalava.reporter.Issues
26 import com.intellij.psi.JavaRecursiveElementVisitor
27 import com.intellij.psi.PsiClassObjectAccessExpression
28 import com.intellij.psi.PsiElement
29 import com.intellij.psi.PsiField
30 import com.intellij.psi.PsiModifier
31 import com.intellij.psi.PsiReferenceExpression
32 import com.intellij.psi.PsiReturnStatement
33 import com.intellij.psi.PsiSynchronizedStatement
34 import com.intellij.psi.PsiThisExpression
35 import org.jetbrains.uast.UAnnotation
36 import org.jetbrains.uast.UCallExpression
37 import org.jetbrains.uast.UClassLiteralExpression
38 import org.jetbrains.uast.UElement
39 import org.jetbrains.uast.UMethod
40 import org.jetbrains.uast.UQualifiedReferenceExpression
41 import org.jetbrains.uast.USimpleNameReferenceExpression
42 import org.jetbrains.uast.UThisExpression
43 import org.jetbrains.uast.UThrowExpression
44 import org.jetbrains.uast.UTryExpression
45 import org.jetbrains.uast.UastErrorType
46 import org.jetbrains.uast.getParentOfType
47 import org.jetbrains.uast.toUElement
48 import org.jetbrains.uast.visitor.AbstractUastVisitor
49 
50 internal class PsiCallableBody(private val callable: PsiCallableItem) : CallableBody {
51 
52     /**
53      * Access [codebase] on demand as [callable] is not properly initialized during initialization
54      * of this class.
55      */
56     private val codebase
57         get() = callable.codebase
58 
59     /**
60      * Access [psiMethod] on demand as [callable] is not properly initialized during initialization
61      * of this class.
62      */
63     private val psiMethod
64         get() = callable.psiMethod
65 
duplicatenull66     override fun duplicate(callableItem: CallableItem): CallableBody {
67         // It is ok to cast here as `duplicate` will always be called with a `callableItem` from the
68         // same type of `Codebase` as this is.
69         return PsiCallableBody(callableItem as PsiCallableItem)
70     }
71 
72     // Cannot create a copy of this as callableItem cannot be cast to PsiCallableItem. There is no
73     // easy way to capture the state of this sufficiently well to implement the necessary behavior
74     // so just pretend it is unavailable for now.
snapshotnull75     override fun snapshot(callableItem: CallableItem): CallableBody {
76         return CallableBody.UNAVAILABLE
77     }
78 
findThrownExceptionsnull79     override fun findThrownExceptions(): Set<ClassItem> {
80         if (!callable.isKotlin()) {
81             return emptySet()
82         }
83 
84         val exceptions = mutableSetOf<ClassItem>()
85 
86         val method = psiMethod as? UMethod ?: return emptySet()
87         method.accept(
88             object : AbstractUastVisitor() {
89                 override fun visitThrowExpression(node: UThrowExpression): Boolean {
90                     val type = node.thrownExpression.getExpressionType()
91                     // TODO: after KTIJ-31242, go back to null check only
92                     if (type != null && type != UastErrorType) {
93                         val typeItemFactory = codebase.globalTypeItemFactory.from(callable)
94                         val exceptionClass = typeItemFactory.getType(type).asClass()
95                         if (exceptionClass != null && !isCaught(exceptionClass, node)) {
96                             exceptions.add(exceptionClass)
97                         }
98                     }
99                     return super.visitThrowExpression(node)
100                 }
101 
102                 private fun isCaught(exceptionClass: ClassItem, node: UThrowExpression): Boolean {
103                     var current: UElement = node
104                     while (true) {
105                         val tryExpression =
106                             current.getParentOfType<UTryExpression>(
107                                 UTryExpression::class.java,
108                                 true,
109                                 UMethod::class.java
110                             )
111                                 ?: return false
112 
113                         for (catchClause in tryExpression.catchClauses) {
114                             for (type in catchClause.types) {
115                                 val qualifiedName = type.canonicalText
116                                 if (exceptionClass.extends(qualifiedName)) {
117                                     return true
118                                 }
119                             }
120                         }
121 
122                         current = tryExpression
123                     }
124                 }
125             }
126         )
127 
128         return exceptions
129     }
130 
findVisiblySynchronizedLocationsnull131     override fun findVisiblySynchronizedLocations(): List<FileLocation> {
132         return buildList {
133             val psiMethod = psiMethod
134             if (psiMethod is UMethod) {
135                 psiMethod.accept(
136                     object : AbstractUastVisitor() {
137                         override fun afterVisitCallExpression(node: UCallExpression) {
138                             super.afterVisitCallExpression(node)
139 
140                             if (node.methodName == "synchronized" && node.receiver == null) {
141                                 val arg = node.valueArguments.firstOrNull()
142                                 if (
143                                     arg is UThisExpression ||
144                                         arg is UClassLiteralExpression ||
145                                         arg is UQualifiedReferenceExpression &&
146                                             arg.receiver is UClassLiteralExpression
147                                 ) {
148                                     val psi = arg.sourcePsi ?: node.sourcePsi ?: node.javaPsi
149                                     add(PsiFileLocation.fromPsiElement(psi))
150                                 }
151                             }
152                         }
153                     }
154                 )
155             } else {
156                 psiMethod.body?.accept(
157                     object : JavaRecursiveElementVisitor() {
158                         override fun visitSynchronizedStatement(
159                             statement: PsiSynchronizedStatement
160                         ) {
161                             super.visitSynchronizedStatement(statement)
162 
163                             val lock = statement.lockExpression
164                             if (
165                                 lock == null ||
166                                     lock is PsiThisExpression ||
167                                     // locking on any class is visible
168                                     lock is PsiClassObjectAccessExpression
169                             ) {
170                                 val psi = lock ?: statement
171                                 add(PsiFileLocation.fromPsiElement(psi))
172                             }
173                         }
174                     }
175                 )
176             }
177         }
178     }
179 
180     /**
181      * Given a method whose return value is annotated with a typedef, runs checks on the typedef and
182      * flags any returned constants not in the list.
183      */
verifyReturnedConstantsnull184     override fun verifyReturnedConstants(
185         typeDefAnnotation: AnnotationItem,
186         typeDefClass: ClassItem,
187     ) {
188         val uAnnotation = typeDefAnnotation.uAnnotation ?: return
189         val body = psiMethod.body ?: return
190 
191         body.accept(
192             object : JavaRecursiveElementVisitor() {
193                 private var constants: List<String>? = null
194 
195                 override fun visitReturnStatement(statement: PsiReturnStatement) {
196                     val value = statement.returnValue
197                     if (value is PsiReferenceExpression) {
198                         val resolved = value.resolve() as? PsiField ?: return
199                         val modifiers = resolved.modifierList ?: return
200                         if (
201                             modifiers.hasModifierProperty(PsiModifier.STATIC) &&
202                                 modifiers.hasModifierProperty(PsiModifier.FINAL)
203                         ) {
204                             if (resolved.type.arrayDimensions > 0) {
205                                 return
206                             }
207                             val name = resolved.name
208 
209                             // Make sure this is one of the allowed annotations
210                             val names =
211                                 constants
212                                     ?: run {
213                                         constants = computeValidConstantNames(uAnnotation)
214                                         constants!!
215                                     }
216                             if (names.isNotEmpty() && !names.contains(name)) {
217                                 val expected = names.joinToString { it }
218                                 codebase.reporter.report(
219                                     Issues.RETURNING_UNEXPECTED_CONSTANT,
220                                     value as PsiElement,
221                                     "Returning unexpected constant $name; is @${typeDefClass.simpleName()} missing this constant? Expected one of $expected"
222                                 )
223                             }
224                         }
225                     }
226                 }
227             }
228         )
229     }
230 
computeValidConstantNamesnull231     private fun computeValidConstantNames(annotation: UAnnotation): List<String> {
232         val constants = annotation.findAttributeValue(ANNOTATION_ATTR_VALUE) ?: return emptyList()
233         if (constants is UCallExpression) {
234             return constants.valueArguments
235                 .mapNotNull { (it as? USimpleNameReferenceExpression)?.identifier }
236                 .toList()
237         }
238 
239         return emptyList()
240     }
241 }
242 
243 /** Public for use only in ExtractAnnotations */
244 val AnnotationItem.uAnnotation: UAnnotation?
245     get() =
246         when (this) {
247             is UAnnotationItem -> uAnnotation
248             is PsiAnnotationItem ->
249                 // Imported annotation
250                 psiAnnotation.toUElement(UAnnotation::class.java)
251             else -> null
252         }
253