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