1 /* <lambda>null2 * Copyright (C) 2017 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 18 19 import com.android.SdkConstants 20 import com.android.tools.lint.annotations.Extractor 21 import com.android.tools.metalava.model.AnnotationItem 22 import com.android.tools.metalava.model.AnnotationTarget 23 import com.android.tools.metalava.model.ClassItem 24 import com.android.tools.metalava.model.Codebase 25 import com.android.tools.metalava.model.FieldItem 26 import com.android.tools.metalava.model.Item 27 import com.android.tools.metalava.model.MemberItem 28 import com.android.tools.metalava.model.MethodItem 29 import com.android.tools.metalava.model.PackageItem 30 import com.android.tools.metalava.model.ParameterItem 31 import com.android.tools.metalava.model.psi.CodePrinter 32 import com.android.tools.metalava.model.psi.PsiAnnotationItem 33 import com.android.tools.metalava.model.psi.PsiClassItem 34 import com.android.tools.metalava.model.psi.PsiMethodItem 35 import com.android.tools.metalava.model.psi.UAnnotationItem 36 import com.android.tools.metalava.model.visitors.ApiVisitor 37 import com.google.common.xml.XmlEscapers 38 import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue 39 import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue 40 import com.intellij.psi.JavaRecursiveElementVisitor 41 import com.intellij.psi.PsiAnnotation 42 import com.intellij.psi.PsiClass 43 import com.intellij.psi.PsiElement 44 import com.intellij.psi.PsiField 45 import com.intellij.psi.PsiModifier 46 import com.intellij.psi.PsiNameValuePair 47 import com.intellij.psi.PsiReferenceExpression 48 import com.intellij.psi.PsiReturnStatement 49 import org.jetbrains.uast.UAnnotation 50 import org.jetbrains.uast.UCallExpression 51 import org.jetbrains.uast.UExpression 52 import org.jetbrains.uast.UReferenceExpression 53 import org.jetbrains.uast.USimpleNameReferenceExpression 54 import org.jetbrains.uast.UastEmptyExpression 55 import org.jetbrains.uast.UastFacade 56 import org.jetbrains.uast.toUElement 57 import java.io.BufferedOutputStream 58 import java.io.File 59 import java.io.FileOutputStream 60 import java.io.PrintWriter 61 import java.io.StringWriter 62 import java.util.jar.JarEntry 63 import java.util.jar.JarOutputStream 64 import kotlin.text.Charsets.UTF_8 65 66 // Like the tools/base Extractor class, but limited to our own (mapped) AnnotationItems, 67 // and only those with source retention (and in particular right now that just means the 68 // typedef annotations.) 69 class ExtractAnnotations( 70 private val codebase: Codebase, 71 private val outputFile: File 72 ) : ApiVisitor() { 73 // Used linked hash map for order such that we always emit parameters after their surrounding method etc 74 private val packageToAnnotationPairs = LinkedHashMap<PackageItem, MutableList<Pair<Item, AnnotationHolder>>>() 75 76 private data class AnnotationHolder( 77 val annotationClass: ClassItem?, 78 val annotationItem: AnnotationItem, 79 val uAnnotation: UAnnotation? 80 ) 81 82 private val fieldNamePrinter = CodePrinter( 83 codebase = codebase, 84 filterReference = filterReference, 85 inlineFieldValues = false, 86 skipUnknown = true 87 ) 88 89 private val fieldValuePrinter = CodePrinter( 90 codebase = codebase, 91 filterReference = filterReference, 92 inlineFieldValues = true, 93 skipUnknown = true 94 ) 95 96 private val classToAnnotationHolder = mutableMapOf<String, AnnotationHolder>() 97 98 fun extractAnnotations() { 99 codebase.accept(this) 100 101 // Write external annotations 102 FileOutputStream(outputFile).use { fileOutputStream -> 103 JarOutputStream(BufferedOutputStream(fileOutputStream)).use { zos -> 104 val sortedPackages = 105 packageToAnnotationPairs.keys.asSequence().sortedBy { it.qualifiedName() }.toList() 106 107 for (pkg in sortedPackages) { 108 // Note: Using / rather than File.separator: jar lib requires it 109 val name = pkg.qualifiedName().replace('.', '/') + "/annotations.xml" 110 111 val outEntry = JarEntry(name) 112 outEntry.time = 0 113 zos.putNextEntry(outEntry) 114 115 val pairs = packageToAnnotationPairs[pkg] ?: continue 116 117 // Ensure stable output 118 if (pairs.size > 1) { 119 pairs.sortBy { it.first.getExternalAnnotationSignature() } 120 } 121 122 StringPrintWriter.create().use { writer -> 123 writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>") 124 125 var open = false 126 var prev: Item? = null 127 for ((item, annotation) in pairs) { 128 if (item != prev) { 129 if (open) { 130 writer.print(" </item>") 131 writer.println() 132 } 133 writer.print(" <item name=\"") 134 writer.print(item.getExternalAnnotationSignature()) 135 writer.println("\">") 136 open = true 137 } 138 prev = item 139 140 writeAnnotation(writer, item, annotation) 141 } 142 if (open) { 143 writer.print(" </item>") 144 writer.println() 145 } 146 writer.println("</root>\n") 147 writer.close() 148 val bytes = writer.contents.toByteArray(UTF_8) 149 zos.write(bytes) 150 zos.closeEntry() 151 } 152 } 153 } 154 } 155 } 156 157 private fun addItem(item: Item, annotation: AnnotationHolder) { 158 val pkg = when (item) { 159 is MemberItem -> item.containingClass().containingPackage() 160 is ParameterItem -> item.containingMethod().containingClass().containingPackage() 161 else -> return 162 } 163 164 val list = packageToAnnotationPairs[pkg] ?: run { 165 val new = 166 mutableListOf<Pair<Item, AnnotationHolder>>() 167 packageToAnnotationPairs[pkg] = new 168 new 169 } 170 list.add(Pair(item, annotation)) 171 } 172 173 override fun visitField(field: FieldItem) { 174 checkItem(field) 175 } 176 177 override fun visitMethod(method: MethodItem) { 178 checkItem(method) 179 } 180 181 override fun visitParameter(parameter: ParameterItem) { 182 checkItem(parameter) 183 } 184 185 /** For a given item, extract the relevant annotations for that item */ 186 private fun checkItem(item: Item) { 187 for (annotation in item.modifiers.annotations()) { 188 val qualifiedName = annotation.qualifiedName ?: continue 189 if (qualifiedName.startsWith(JAVA_LANG_PREFIX) || 190 qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) || 191 qualifiedName.startsWith(ANDROID_ANNOTATION_PREFIX) 192 ) { 193 if (annotation.isTypeDefAnnotation()) { 194 // Imported typedef 195 addItem(item, AnnotationHolder(null, annotation, null)) 196 } else if (annotation.targets.contains(AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE)) { 197 addItem(item, AnnotationHolder(null, annotation, null)) 198 } 199 200 continue 201 } else if (qualifiedName.startsWith(ORG_JETBRAINS_ANNOTATIONS_PREFIX) || 202 qualifiedName.startsWith(ORG_INTELLIJ_LANG_ANNOTATIONS_PREFIX) 203 ) { 204 // Externally merged metadata, like @Contract and @Language 205 addItem(item, AnnotationHolder(null, annotation, null)) 206 continue 207 } 208 209 val typeDefClass = annotation.resolve() ?: continue 210 val className = typeDefClass.qualifiedName() 211 if (typeDefClass.isAnnotationType()) { 212 val cached = classToAnnotationHolder[className] 213 if (cached != null) { 214 addItem(item, cached) 215 continue 216 } 217 218 val typeDefAnnotation = typeDefClass.modifiers.annotations().firstOrNull { 219 it.isTypeDefAnnotation() 220 } 221 if (typeDefAnnotation != null) { 222 // Make sure it has the right retention 223 if (!hasSourceRetention(typeDefClass)) { 224 reporter.report( 225 Issues.ANNOTATION_EXTRACTION, typeDefClass, 226 "This typedef annotation class should have @Retention(RetentionPolicy.SOURCE)" 227 ) 228 } 229 230 if (filterEmit.test(typeDefClass)) { 231 reporter.report( 232 Issues.ANNOTATION_EXTRACTION, typeDefClass, 233 "This typedef annotation class should be marked @hide or should not be marked public" 234 ) 235 } 236 237 val result = 238 if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) { 239 AnnotationHolder( 240 typeDefClass, typeDefAnnotation, 241 UastFacade.convertElement( 242 typeDefAnnotation.psiAnnotation, 243 null, 244 UAnnotation::class.java 245 ) as UAnnotation 246 ) 247 } else if (typeDefAnnotation is UAnnotationItem && typeDefClass is PsiClassItem) { 248 AnnotationHolder( 249 typeDefClass, typeDefAnnotation, typeDefAnnotation.uAnnotation 250 ) 251 } else { 252 continue 253 } 254 255 classToAnnotationHolder[className] = result 256 addItem(item, result) 257 258 if (item is PsiMethodItem && result.uAnnotation != null && 259 !reporter.isSuppressed(Issues.RETURNING_UNEXPECTED_CONSTANT) 260 ) { 261 verifyReturnedConstants(item, result.uAnnotation, result, className) 262 } 263 continue 264 } 265 } 266 } 267 } 268 269 /** 270 * Given a method whose return value is annotated with a typedef, runs checks on the typedef 271 * and flags any returned constants not in the list. 272 */ 273 private fun verifyReturnedConstants( 274 item: PsiMethodItem, 275 uAnnotation: UAnnotation, 276 result: AnnotationHolder, 277 className: String 278 ) { 279 val method = item.psiMethod 280 if (method.body != null) { 281 method.body?.accept(object : JavaRecursiveElementVisitor() { 282 private var constants: List<String>? = null 283 284 override fun visitReturnStatement(statement: PsiReturnStatement) { 285 val value = statement.returnValue 286 if (value is PsiReferenceExpression) { 287 val resolved = value.resolve() as? PsiField ?: return 288 val modifiers = resolved.modifierList ?: return 289 if (modifiers.hasModifierProperty(PsiModifier.STATIC) && 290 modifiers.hasModifierProperty(PsiModifier.FINAL) 291 ) { 292 if (resolved.type.arrayDimensions > 0) { 293 return 294 } 295 val name = resolved.name 296 297 // Make sure this is one of the allowed annotations 298 val names = constants ?: run { 299 constants = computeValidConstantNames(uAnnotation) 300 constants!! 301 } 302 if (names.isNotEmpty() && !names.contains(name)) { 303 val expected = names.joinToString { it } 304 reporter.report( 305 Issues.RETURNING_UNEXPECTED_CONSTANT, value as PsiElement, 306 "Returning unexpected constant $name; is @${result.annotationClass?.simpleName() 307 ?: className} missing this constant? Expected one of $expected" 308 ) 309 } 310 } 311 } 312 } 313 }) 314 } 315 } 316 317 private fun computeValidConstantNames(annotation: UAnnotation): List<String> { 318 val constants = annotation.findAttributeValue(SdkConstants.ATTR_VALUE) ?: return emptyList() 319 if (constants is UCallExpression) { 320 return constants.valueArguments.mapNotNull { (it as? USimpleNameReferenceExpression)?.identifier }.toList() 321 } 322 323 return emptyList() 324 } 325 326 private fun hasSourceRetention(annotationClass: ClassItem): Boolean { 327 if (annotationClass is PsiClassItem) { 328 return hasSourceRetention(annotationClass.psiClass) 329 } 330 return false 331 } 332 333 private fun hasSourceRetention(cls: PsiClass): Boolean { 334 val modifierList = cls.modifierList 335 if (modifierList != null) { 336 for (psiAnnotation in modifierList.annotations) { 337 val uAnnotation = psiAnnotation.toUElement(UAnnotation::class.java) 338 val hasSourceRetention = 339 if (uAnnotation != null) { 340 hasSourceRetention(uAnnotation) 341 } else { 342 // There is a hole in UAST conversion. If so, fall back to using PSI. 343 hasSourceRetention(psiAnnotation) 344 } 345 if (hasSourceRetention) { 346 return true 347 } 348 } 349 } 350 351 return false 352 } 353 354 private fun hasSourceRetention(annotation: UAnnotation): Boolean { 355 val qualifiedName = annotation.qualifiedName 356 if (isRetention(qualifiedName)) { 357 val attributes = annotation.attributeValues 358 if (attributes.size != 1) { 359 reporter.report( 360 Issues.ANNOTATION_EXTRACTION, annotation.sourcePsi, 361 "Expected exactly one parameter passed to @Retention" 362 ) 363 return false 364 } 365 val value = attributes[0].expression 366 if (value is UReferenceExpression) { 367 try { 368 val element = value.resolve() 369 if (element is PsiField) { 370 val field = element as PsiField? 371 if (SOURCE == field!!.name) { 372 return true 373 } 374 } 375 } catch (t: Throwable) { 376 val s = value.asSourceString() 377 return s.contains(SOURCE) 378 } 379 } 380 } 381 382 return false 383 } 384 385 private fun hasSourceRetention(psiAnnotation: PsiAnnotation): Boolean { 386 val qualifiedName = psiAnnotation.qualifiedName 387 if (isRetention(qualifiedName)) { 388 val attributes = psiAnnotation.parameterList.attributes 389 if (attributes.size != 1) { 390 reporter.report( 391 Issues.ANNOTATION_EXTRACTION, psiAnnotation, 392 "Expected exactly one parameter passed to @Retention" 393 ) 394 return false 395 } 396 return when (val value = attributes[0].attributeValue) { 397 is JvmAnnotationEnumFieldValue -> SOURCE == value.fieldName 398 is JvmAnnotationConstantValue -> 399 (value.constantValue as? String)?.contains(SOURCE) ?: false 400 else -> false 401 } 402 } 403 404 return false 405 } 406 407 /** 408 * A writer which stores all its contents into a string and has the ability to mark a certain 409 * freeze point and then reset back to it 410 */ 411 private class StringPrintWriter constructor(private val stringWriter: StringWriter) : 412 PrintWriter(stringWriter) { 413 private var mark: Int = 0 414 415 val contents: String get() = stringWriter.toString() 416 417 fun mark() { 418 flush() 419 mark = stringWriter.buffer.length 420 } 421 422 fun reset() { 423 stringWriter.buffer.setLength(mark) 424 } 425 426 override fun toString(): String { 427 return contents 428 } 429 430 companion object { 431 fun create(): StringPrintWriter { 432 return StringPrintWriter(StringWriter(1000)) 433 } 434 } 435 } 436 437 private fun escapeXml(unescaped: String): String { 438 return XmlEscapers.xmlAttributeEscaper().escape(unescaped) 439 } 440 441 private fun Item.getExternalAnnotationSignature(): String? { 442 when (this) { 443 is PackageItem -> { 444 return escapeXml(qualifiedName()) 445 } 446 447 is ClassItem -> { 448 return escapeXml(qualifiedName()) 449 } 450 451 is MethodItem -> { 452 val sb = StringBuilder(100) 453 sb.append(escapeXml(containingClass().qualifiedName())) 454 sb.append(' ') 455 456 if (isConstructor()) { 457 sb.append(escapeXml(containingClass().simpleName())) 458 } else { 459 sb.append(escapeXml(returnType().toTypeString())) 460 sb.append(' ') 461 sb.append(escapeXml(name())) 462 } 463 464 sb.append('(') 465 466 // The signature must match *exactly* the formatting used by IDEA, 467 // since it looks up external annotations in a map by this key. 468 // Therefore, it is vital that the parameter list uses exactly one 469 // space after each comma between parameters, and *no* spaces between 470 // generics variables, e.g. foo(Map<A,B>, int) 471 var i = 0 472 val parameterList = parameters() 473 val n = parameterList.size 474 while (i < n) { 475 if (i > 0) { 476 sb.append(',').append(' ') 477 } 478 val type = parameterList[i].type().toTypeString() 479 .replace(" ", "") 480 .replace("?extends", "? extends ") 481 .replace("?super", "? super ") 482 sb.append(escapeXml(type)) 483 i++ 484 } 485 sb.append(')') 486 return sb.toString() 487 } 488 489 is FieldItem -> { 490 return escapeXml(containingClass().qualifiedName()) + " " + name() 491 } 492 493 is ParameterItem -> { 494 return containingMethod().getExternalAnnotationSignature() + " " + this.parameterIndex 495 } 496 } 497 498 return null 499 } 500 501 private fun writeAnnotation( 502 writer: StringPrintWriter, 503 item: Item, 504 annotationHolder: AnnotationHolder 505 ) { 506 val annotationItem = annotationHolder.annotationItem 507 val uAnnotation = annotationHolder.uAnnotation 508 ?: when (annotationItem) { 509 is UAnnotationItem -> annotationItem.uAnnotation 510 is PsiAnnotationItem -> 511 // Imported annotation 512 annotationItem.psiAnnotation.toUElement(UAnnotation::class.java) ?: return 513 else -> return 514 } 515 val qualifiedName = annotationItem.qualifiedName 516 517 writer.mark() 518 writer.print(" <annotation name=\"") 519 writer.print(qualifiedName) 520 521 var attributes = uAnnotation.attributeValues 522 if (attributes.isEmpty()) { 523 writer.print("\"/>") 524 writer.println() 525 return 526 } 527 528 writer.print("\">") 529 writer.println() 530 531 // noinspection PointlessBooleanExpression,ConstantConditions 532 if (sortAnnotations) { 533 // Ensure that the value attribute is written first 534 attributes = attributes.sortedWith( 535 compareBy( 536 { (it.name ?: SdkConstants.ATTR_VALUE) != SdkConstants.ATTR_VALUE }, 537 { it.name } 538 ) 539 ) 540 } 541 542 if (attributes.size == 1 && Extractor.REQUIRES_PERMISSION.isPrefix(qualifiedName, true)) { 543 val expression = attributes[0].expression 544 if (expression is UAnnotation) { 545 // The external annotations format does not allow for nested/complex annotations. 546 // However, these special annotations (@RequiresPermission.Read, 547 // @RequiresPermission.Write, etc) are known to only be simple containers with a 548 // single permission child, so instead we "inline" the content: 549 // @Read(@RequiresPermission(allOf={P1,P2},conditional=true) 550 // => 551 // @RequiresPermission.Read(allOf({P1,P2},conditional=true) 552 // That's setting attributes that don't actually exist on the container permission, 553 // but we'll counteract that on the read-annotations side. 554 val annotation = expression as UAnnotation 555 attributes = annotation.attributeValues 556 } else if (expression is UCallExpression) { 557 val nestedPsi = expression.sourcePsi as? PsiAnnotation 558 val annotation = nestedPsi?.let { 559 UastFacade.convertElement(it, expression, UAnnotation::class.java) 560 } as? UAnnotation 561 annotation?.attributeValues?.let { attributes = it } 562 } else if (expression is UastEmptyExpression && attributes[0].sourcePsi is PsiNameValuePair) { 563 val memberValue = (attributes[0].sourcePsi as PsiNameValuePair).value 564 if (memberValue is PsiAnnotation) { 565 val annotation = memberValue.toUElement(UAnnotation::class.java) 566 annotation?.attributeValues?.let { attributes = it } 567 } 568 } 569 } 570 571 val inlineConstants = isInlinedConstant(annotationItem) 572 var empty = true 573 for (pair in attributes) { 574 val expression = pair.expression 575 val value = attributeString(expression, inlineConstants) ?: continue 576 empty = false 577 var name = pair.name 578 if (name == null) { 579 name = SdkConstants.ATTR_VALUE // default name 580 } 581 582 // Platform typedef annotations now declare a prefix attribute for 583 // documentation generation purposes; this should not be part of the 584 // extracted metadata. 585 if (("prefix" == name || "suffix" == name) && annotationItem.isTypeDefAnnotation()) { 586 reporter.report( 587 Issues.SUPERFLUOUS_PREFIX, item, 588 "Superfluous $name attribute on typedef" 589 ) 590 continue 591 } 592 593 writer.print(" <val name=\"") 594 writer.print(name) 595 writer.print("\" val=\"") 596 writer.print(escapeXml(value)) 597 writer.println("\" />") 598 } 599 600 if (empty && attributes.isNotEmpty()) { 601 // All items were filtered out: don't write the annotation at all 602 writer.reset() 603 return 604 } 605 606 writer.println(" </annotation>") 607 } 608 609 private fun attributeString( 610 value: UExpression?, 611 inlineConstants: Boolean 612 ): String? { 613 val printer = 614 if (inlineConstants) { 615 fieldValuePrinter 616 } else { 617 fieldNamePrinter 618 } 619 620 return printer.toSourceString(value) 621 } 622 623 private fun isInlinedConstant(annotationItem: AnnotationItem): Boolean { 624 return annotationItem.isTypeDefAnnotation() 625 } 626 627 /** Whether to sort annotation attributes (otherwise their declaration order is used) */ 628 private val sortAnnotations: Boolean = true 629 630 companion object { 631 private const val SOURCE = "SOURCE" 632 } 633 } 634