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.tools.metalava.doclava1.ApiPredicate 20 import com.android.tools.metalava.doclava1.Issues 21 import com.android.tools.metalava.model.AnnotationAttributeValue 22 import com.android.tools.metalava.model.ClassItem 23 import com.android.tools.metalava.model.Codebase 24 import com.android.tools.metalava.model.ConstructorItem 25 import com.android.tools.metalava.model.FieldItem 26 import com.android.tools.metalava.model.Item 27 import com.android.tools.metalava.model.MethodItem 28 import com.android.tools.metalava.model.PackageItem 29 import com.android.tools.metalava.model.PackageList 30 import com.android.tools.metalava.model.ParameterItem 31 import com.android.tools.metalava.model.TypeItem 32 import com.android.tools.metalava.model.VisibilityLevel 33 import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION 34 import com.android.tools.metalava.model.visitors.ApiVisitor 35 import com.android.tools.metalava.model.visitors.ItemVisitor 36 import java.util.ArrayList 37 import java.util.HashMap 38 import java.util.HashSet 39 import java.util.function.Predicate 40 41 /** 42 * The [ApiAnalyzer] is responsible for walking over the various 43 * classes and members and compute visibility etc of the APIs 44 */ 45 class ApiAnalyzer( 46 /** The code to analyze */ 47 private val codebase: Codebase 48 ) { 49 /** All packages in the API */ 50 private val packages: PackageList = codebase.getPackages() 51 52 fun computeApi() { 53 if (codebase.trustedApi()) { 54 // The codebase is already an API; no consistency checks to be performed 55 return 56 } 57 58 // Apply options for packages that should be hidden 59 hidePackages() 60 skipEmitPackages() 61 62 // Propagate visibility down into individual elements -- if a class is hidden, 63 // then the methods and fields are hidden etc 64 propagateHiddenRemovedAndDocOnly(false) 65 } 66 67 fun addConstructors(filter: Predicate<Item>) { 68 // Let's say I have 69 // class GrandParent { public GrandParent(int) {} } 70 // class Parent { Parent(int) {} } 71 // class Child { public Child(int) {} } 72 // 73 // Here Parent's constructor is not public. For normal stub generation I'd end up with this: 74 // class GrandParent { public GrandParent(int) {} } 75 // class Parent { } 76 // class Child { public Child(int) {} } 77 // 78 // This doesn't compile - Parent can't have a default constructor since there isn't 79 // one for it to invoke on GrandParent. 80 // 81 // I can generate a fake constructor instead, such as 82 // Parent() { super(0); } 83 // 84 // But it's hard to do this lazily; what if I'm generating the Child class first? 85 // Therefore, we'll instead walk over the hierarchy and insert these constructors 86 // into the Item hierarchy such that code generation can find them. 87 // 88 // (We also need to handle the throws list, so we can't just unconditionally 89 // insert package private constructors 90 // 91 // To do this right I really need to process super constructors before the classes 92 // depending on them. 93 94 // Mark all classes that are the super class of some other class: 95 val allClasses = packages.allClasses().filter { filter.test(it) } 96 97 codebase.clearTags() 98 allClasses.forEach { cls -> 99 cls.superClass()?.tag = true 100 } 101 102 val leafClasses = allClasses.filter { !it.tag }.toList() 103 104 // Now walk through all the leaf classes, and walk up the super hierarchy 105 // and recursively add constructors; we'll do it recursively to make sure that 106 // the superclass has had its constructors initialized first (such that we can 107 // match the parameter lists and throws signatures), and we use the tag fields 108 // to avoid looking at all the internal classes more than once. 109 codebase.clearTags() 110 leafClasses 111 // Filter classes by filter here to not waste time in hidden packages 112 .filter { filter.test(it) } 113 .forEach { addConstructors(it, filter) } 114 } 115 116 /** 117 * Handle computing constructor hierarchy. We'll be setting several attributes: 118 * [ClassItem.stubConstructor] : The default constructor to invoke in this 119 * class from subclasses. **NOTE**: This constructor may not be part of 120 * the [ClassItem.constructors] list, e.g. for package private default constructors 121 * we've inserted (because there were no public constructors or constructors not 122 * using hidden parameter types.) 123 * 124 * If we can find a public constructor we'll put that here instead. 125 * 126 * [ConstructorItem.superConstructor] The default constructor to invoke. If set, 127 * use this rather than the [ClassItem.stubConstructor]. 128 * 129 * [Item.tag] : mark for avoiding repeated iteration of internal item nodes 130 * 131 * 132 */ 133 private fun addConstructors(cls: ClassItem, filter: Predicate<Item>) { 134 // What happens if we have 135 // package foo: 136 // public class A { public A(int) } 137 // package bar 138 // public class B extends A { public B(int) } 139 // If I just try inserting package private constructors here things will NOT work: 140 // package foo: 141 // public class A { public A(int); A() {} } 142 // package bar 143 // public class B extends A { public B(int); B() } 144 // because A <() is not accessible from B() -- it's outside the same package. 145 // 146 // So, I'll need to model the real constructors for all the scenarios where that 147 // works. 148 // 149 // The remaining challenge is that there will be some gaps: when I don't have 150 // a default constructor, subclass constructors will have to have an explicit 151 // super(args) call to pick the parent constructor to use. And which one? 152 // It generally doesn't matter; just pick one, but unfortunately, the super 153 // constructor can throw exceptions, and in that case the subclass constructor 154 // must also throw all those constructors (you can't surround a super call 155 // with try/catch.) Luckily, the source code already needs to do this to 156 // compile, so we can just use the same constructor as the super call. 157 // But there are two cases we have to deal with: 158 // (1) the constructor doesn't call a super constructor; it calls another 159 // constructor on this class. 160 // (2) the super constructor it *does* call isn't available. 161 // 162 // For (1), this means that our stub code generator should be prepared to 163 // handle both super- and this- dispatches; we'll handle this by pointing 164 // it to the constructor to use, and it checks to see if the containing class 165 // for the constructor is the same to decide whether to emit "this" or "super". 166 167 if (cls.tag || !cls.isClass()) { // Don't add constructors to interfaces, enums, annotations, etc 168 return 169 } 170 171 // First handle its super class hierarchy to make sure that we've 172 // already constructed super classes 173 val superClass = cls.filteredSuperclass(filter) 174 superClass?.let { addConstructors(it, filter) } 175 cls.tag = true 176 177 if (superClass != null) { 178 val superDefaultConstructor = superClass.stubConstructor 179 if (superDefaultConstructor != null) { 180 val constructors = cls.constructors() 181 for (constructor in constructors) { 182 val superConstructor = constructor.superConstructor 183 if (superConstructor == null || 184 (superConstructor.containingClass() != superClass && 185 superConstructor.containingClass() != cls) 186 ) { 187 constructor.superConstructor = superDefaultConstructor 188 } 189 } 190 } 191 } 192 193 // Find default constructor, if one doesn't exist 194 val allConstructors = cls.constructors() 195 if (allConstructors.isNotEmpty()) { 196 197 // Try and use a publicly accessible constructor first. 198 val constructors = cls.filteredConstructors(filter).toList() 199 if (!constructors.isEmpty()) { 200 // Try to pick the constructor, select first by fewest throwables, then fewest parameters, 201 // then based on order in listFilter.test(cls) 202 cls.stubConstructor = constructors.reduce { first, second -> pickBest(first, second) } 203 return 204 } 205 206 // No accessible constructors are available so one will have to be created, either a private constructor to 207 // prevent instances of the class from being created, or a package private constructor for use by subclasses 208 // in the package to use. Subclasses outside the package would need a protected or public constructor which 209 // would already be part of the API so should have dropped out above. 210 // 211 // The visibility levels on the constructors from the source can give a clue as to what is required. e.g. 212 // if all constructors are private then it is ok for the generated constructor to be private, otherwise it 213 // should be package private. 214 val allPrivate = allConstructors.asSequence() 215 .map { it.isPrivate } 216 .reduce { v1, v2 -> v1 and v2 } 217 218 val visibilityLevel = if (allPrivate) VisibilityLevel.PRIVATE else VisibilityLevel.PACKAGE_PRIVATE 219 220 // No constructors, yet somebody extends this (or private constructor): we have to invent one, such that 221 // subclasses can dispatch to it in the stub files etc 222 cls.stubConstructor = cls.createDefaultConstructor().also { 223 it.mutableModifiers().setVisibilityLevel(visibilityLevel) 224 it.hidden = false 225 it.superConstructor = superClass?.stubConstructor 226 } 227 } 228 } 229 230 // TODO: Annotation test: @ParameterName, if present, must be supplied on *all* the arguments! 231 // Warn about @DefaultValue("null"); they probably meant @DefaultNull 232 // Supplying default parameter in override is not allowed! 233 234 private fun pickBest( 235 current: ConstructorItem, 236 next: ConstructorItem 237 ): ConstructorItem { 238 val currentThrowsCount = current.throwsTypes().size 239 val nextThrowsCount = next.throwsTypes().size 240 241 return if (currentThrowsCount < nextThrowsCount) { 242 current 243 } else if (currentThrowsCount > nextThrowsCount) { 244 next 245 } else { 246 val currentParameterCount = current.parameters().size 247 val nextParameterCount = next.parameters().size 248 if (currentParameterCount <= nextParameterCount) { 249 current 250 } else next 251 } 252 } 253 254 fun generateInheritedStubs(filterEmit: Predicate<Item>, filterReference: Predicate<Item>) { 255 // When analyzing libraries we may discover some new classes during traversal; these aren't 256 // part of the API but may be super classes or interfaces; these will then be added into the 257 // package class lists, which could trigger a concurrent modification, so create a snapshot 258 // of the class list and iterate over it: 259 val allClasses = packages.allClasses().toList() 260 allClasses.forEach { 261 if (filterEmit.test(it)) { 262 generateInheritedStubs(it, filterEmit, filterReference) 263 } 264 } 265 } 266 267 private fun generateInheritedStubs(cls: ClassItem, filterEmit: Predicate<Item>, filterReference: Predicate<Item>) { 268 if (!cls.isClass()) return 269 if (cls.superClass() == null) return 270 val superClasses: Sequence<ClassItem> = generateSequence(cls.superClass()) { it.superClass() } 271 val hiddenSuperClasses: Sequence<ClassItem> = 272 superClasses.filter { !filterReference.test(it) && !it.isJavaLangObject() } 273 274 if (hiddenSuperClasses.none()) { // not missing any implementation methods 275 return 276 } 277 278 addInheritedStubsFrom(cls, hiddenSuperClasses, superClasses, filterEmit, filterReference) 279 addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference) 280 } 281 282 private fun addInheritedInterfacesFrom( 283 cls: ClassItem, 284 hiddenSuperClasses: Sequence<ClassItem>, 285 filterReference: Predicate<Item> 286 ) { 287 var interfaceTypes: MutableList<TypeItem>? = null 288 var interfaceTypeClasses: MutableList<ClassItem>? = null 289 for (hiddenSuperClass in hiddenSuperClasses) { 290 for (hiddenInterface in hiddenSuperClass.interfaceTypes()) { 291 val hiddenInterfaceClass = hiddenInterface.asClass() 292 if (filterReference.test(hiddenInterfaceClass ?: continue)) { 293 if (interfaceTypes == null) { 294 interfaceTypes = cls.interfaceTypes().toMutableList() 295 interfaceTypeClasses = 296 interfaceTypes.asSequence().map { it.asClass() }.filterNotNull().toMutableList() 297 if (cls.isInterface()) { 298 cls.superClass()?.let { interfaceTypeClasses.add(it) } 299 } 300 cls.setInterfaceTypes(interfaceTypes) 301 } 302 if (interfaceTypeClasses!!.any { it == hiddenInterfaceClass }) { 303 continue 304 } 305 306 interfaceTypeClasses.add(hiddenInterfaceClass) 307 308 if (hiddenInterfaceClass.hasTypeVariables()) { 309 val mapping = cls.mapTypeVariables(hiddenSuperClass) 310 if (mapping.isNotEmpty()) { 311 val mappedType: TypeItem = hiddenInterface.convertType(mapping, cls) 312 interfaceTypes.add(mappedType) 313 continue 314 } 315 } 316 317 interfaceTypes.add(hiddenInterface) 318 } 319 } 320 } 321 } 322 323 private fun addInheritedStubsFrom( 324 cls: ClassItem, 325 hiddenSuperClasses: Sequence<ClassItem>, 326 superClasses: Sequence<ClassItem>, 327 filterEmit: Predicate<Item>, 328 filterReference: Predicate<Item> 329 ) { 330 331 // Also generate stubs for any methods we would have inherited from abstract parents 332 // All methods from super classes that (1) aren't overridden in this class already, and 333 // (2) are overriding some method that is in a public interface accessible from this class. 334 val interfaces: Set<TypeItem> = cls.allInterfaceTypes(filterReference).asSequence().toSet() 335 336 // Note that we can't just call method.superMethods() to and see whether any of their containing 337 // classes are among our target APIs because it's possible that the super class doesn't actually 338 // implement the interface, but still provides a matching signature for the interface. 339 // Instead we'll look through all of our interface methods and look for potential overrides 340 val interfaceNames = mutableMapOf<String, MutableList<MethodItem>>() 341 for (interfaceType in interfaces) { 342 val interfaceClass = interfaceType.asClass() ?: continue 343 for (method in interfaceClass.methods()) { 344 val name = method.name() 345 val list = interfaceNames[name] ?: run { 346 val list = ArrayList<MethodItem>() 347 interfaceNames[name] = list 348 list 349 } 350 list.add(method) 351 } 352 } 353 354 // Also add in any abstract methods from public super classes 355 val publicSuperClasses = superClasses.filter { filterEmit.test(it) && !it.isJavaLangObject() } 356 for (superClass in publicSuperClasses) { 357 for (method in superClass.methods()) { 358 if (!method.modifiers.isAbstract() || !method.modifiers.isPublicOrProtected()) { 359 continue 360 } 361 val name = method.name() 362 val list = interfaceNames[name] ?: run { 363 val list = ArrayList<MethodItem>() 364 interfaceNames[name] = list 365 list 366 } 367 list.add(method) 368 } 369 } 370 371 // Also add in any concrete public methods from hidden super classes 372 for (superClass in hiddenSuperClasses) { 373 374 // Determine if there is a non-hidden class between the superClass and this class. 375 // If non hidden classes are found, don't include the methods for this hiddenSuperClass, 376 // as it will already have been included in a previous super class 377 var includeHiddenSuperClassMethods = true 378 var currentClass = cls.superClass() 379 while (currentClass != superClass && currentClass != null) { 380 if (!hiddenSuperClasses.contains(currentClass)) { 381 includeHiddenSuperClassMethods = false 382 break 383 } 384 currentClass = currentClass.superClass() 385 } 386 387 if (!includeHiddenSuperClassMethods) { 388 continue 389 } 390 391 for (method in superClass.methods()) { 392 if (method.modifiers.isAbstract() || !method.modifiers.isPublic()) { 393 continue 394 } 395 396 if (method.hasHiddenType(filterReference)) { 397 continue 398 } 399 400 val name = method.name() 401 val list = interfaceNames[name] ?: run { 402 val list = ArrayList<MethodItem>() 403 interfaceNames[name] = list 404 list 405 } 406 list.add(method) 407 } 408 } 409 410 // Find all methods that are inherited from these classes into our class 411 // (making sure that we don't have duplicates, e.g. a method defined by one 412 // inherited class and then overridden by another closer one). 413 // map from method name to super methods overriding our interfaces 414 val map = HashMap<String, MutableList<MethodItem>>() 415 416 for (superClass in hiddenSuperClasses) { 417 for (method in superClass.methods()) { 418 val modifiers = method.modifiers 419 if (!modifiers.isPrivate() && !modifiers.isAbstract()) { 420 val name = method.name() 421 val candidates = interfaceNames[name] ?: continue 422 val parameterCount = method.parameters().size 423 for (superMethod in candidates) { 424 if (parameterCount != superMethod.parameters().count()) { 425 continue 426 } 427 if (method.matches(superMethod)) { 428 val list = map[name] ?: run { 429 val newList = ArrayList<MethodItem>() 430 map[name] = newList 431 newList 432 } 433 list.add(method) 434 break 435 } 436 } 437 } 438 } 439 } 440 441 // Remove any methods that are overriding any of our existing methods 442 for (method in cls.methods()) { 443 val name = method.name() 444 val candidates = map[name] ?: continue 445 val iterator = candidates.listIterator() 446 while (iterator.hasNext()) { 447 val inheritedMethod = iterator.next() 448 if (method.matches(inheritedMethod)) { 449 iterator.remove() 450 } 451 } 452 } 453 454 // Next remove any overrides among the remaining super methods (e.g. one method from a hidden parent is 455 // overriding another method from a more distant hidden parent). 456 map.values.forEach { methods -> 457 if (methods.size >= 2) { 458 for (candidate in ArrayList(methods)) { 459 for (superMethod in candidate.allSuperMethods()) { 460 methods.remove(superMethod) 461 } 462 } 463 } 464 } 465 466 // We're now left with concrete methods in hidden parents that are implementing methods in public 467 // interfaces that are listed in this class. Create stubs for them: 468 map.values.flatten().forEach { 469 val method = cls.createMethod(it) 470 /* Insert comment marker: This is useful for debugging purposes but doesn't 471 belong in the stub 472 method.documentation = "// Inlined stub from hidden parent class ${it.containingClass().qualifiedName()}\n" + 473 method.documentation 474 */ 475 method.inheritedMethod = true 476 method.inheritedFrom = it.containingClass() 477 478 // The documentation may use relative references to classes in import statements 479 // in the original class, so expand the documentation to be fully qualified. 480 @Suppress("ConstantConditionIf") 481 if (!EXPAND_DOCUMENTATION) { 482 method.documentation = it.fullyQualifiedDocumentation() 483 } 484 cls.addMethod(method) 485 } 486 } 487 488 /** Hide packages explicitly listed in [Options.hidePackages] */ 489 private fun hidePackages() { 490 for (pkgName in options.hidePackages) { 491 val pkg = codebase.findPackage(pkgName) ?: continue 492 pkg.hidden = true 493 pkg.included = false // because included has already been initialized 494 } 495 } 496 497 /** Apply emit filters listed in [Options.skipEmitPackages] */ 498 private fun skipEmitPackages() { 499 for (pkgName in options.skipEmitPackages) { 500 val pkg = codebase.findPackage(pkgName) ?: continue 501 pkg.emit = false 502 } 503 } 504 505 /** 506 * Merge in external qualifier annotations (i.e. ones intended to be included in the API written 507 * from all configured sources. 508 */ 509 fun mergeExternalQualifierAnnotations() { 510 if (!options.mergeQualifierAnnotations.isEmpty()) { 511 AnnotationsMerger(codebase).mergeQualifierAnnotations(options.mergeQualifierAnnotations) 512 } 513 } 514 515 /** Merge in external show/hide annotations from all configured sources */ 516 fun mergeExternalInclusionAnnotations() { 517 if (!options.mergeInclusionAnnotations.isEmpty()) { 518 AnnotationsMerger(codebase).mergeInclusionAnnotations(options.mergeInclusionAnnotations) 519 } 520 } 521 522 /** 523 * Propagate the hidden flag down into individual elements -- if a class is hidden, then the methods and fields 524 * are hidden etc 525 */ 526 private fun propagateHiddenRemovedAndDocOnly(includingFields: Boolean) { 527 packages.accept(object : ItemVisitor(visitConstructorsAsMethods = true, nestInnerClasses = true) { 528 override fun visitPackage(pkg: PackageItem) { 529 when { 530 options.hidePackages.contains(pkg.qualifiedName()) -> pkg.hidden = true 531 pkg.modifiers.hasShowAnnotation() -> pkg.hidden = false 532 pkg.modifiers.hasHideAnnotations() -> pkg.hidden = true 533 } 534 val containingPackage = pkg.containingPackage() 535 if (containingPackage != null) { 536 if (containingPackage.hidden && !containingPackage.isDefault) { 537 pkg.hidden = true 538 } 539 if (containingPackage.docOnly) { 540 pkg.docOnly = true 541 } 542 } 543 } 544 545 override fun visitClass(cls: ClassItem) { 546 val containingClass = cls.containingClass() 547 if (cls.modifiers.hasShowAnnotation()) { 548 cls.hidden = false 549 // Make containing package non-hidden if it contains a show-annotation 550 // class. Doclava does this in PackageInfo.isHidden(). 551 cls.containingPackage().hidden = false 552 if (cls.containingClass() != null) { 553 ensureParentVisible(cls) 554 } 555 } else if (cls.modifiers.hasHideAnnotations()) { 556 cls.hidden = true 557 } else if (containingClass != null) { 558 if (containingClass.hidden) { 559 cls.hidden = true 560 } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) { 561 // See explanation in visitMethod 562 cls.hidden = true 563 } 564 if (containingClass.docOnly) { 565 cls.docOnly = true 566 } 567 if (containingClass.removed) { 568 cls.removed = true 569 } 570 } else { 571 val containingPackage = cls.containingPackage() 572 if (containingPackage.hidden && !containingPackage.isDefault) { 573 cls.hidden = true 574 } else if (containingPackage.originallyHidden) { 575 // Package was marked hidden; it's been unhidden by some other 576 // classes (marked with show annotations) but this class 577 // should continue to default. 578 cls.hidden = true 579 } 580 if (containingPackage.docOnly && !containingPackage.isDefault) { 581 cls.docOnly = true 582 } 583 if (containingPackage.removed && !cls.modifiers.hasShowAnnotation()) { 584 cls.removed = true 585 } 586 } 587 } 588 589 override fun visitMethod(method: MethodItem) { 590 if (method.modifiers.hasShowAnnotation()) { 591 method.hidden = false 592 ensureParentVisible(method) 593 } else if (method.modifiers.hasHideAnnotations()) { 594 method.hidden = true 595 } else { 596 val containingClass = method.containingClass() 597 if (containingClass.hidden) { 598 method.hidden = true 599 } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) { 600 // This is a member in a class that was hidden but then unhidden; 601 // but it was unhidden by a non-recursive (single) show annotation, so 602 // don't inherit the show annotation into this item. 603 method.hidden = true 604 } 605 if (containingClass.docOnly) { 606 method.docOnly = true 607 } 608 if (containingClass.removed) { 609 method.removed = true 610 } 611 } 612 } 613 614 override fun visitField(field: FieldItem) { 615 if (field.modifiers.hasShowAnnotation()) { 616 field.hidden = false 617 ensureParentVisible(field) 618 } else if (field.modifiers.hasHideAnnotations()) { 619 field.hidden = true 620 } else { 621 val containingClass = field.containingClass() 622 /* We don't always propagate field visibility down to the fields 623 because we sometimes move fields around, and in that 624 case we don't want to carry forward the "hidden" attribute 625 from the field that wasn't marked on the field but its 626 container interface. 627 */ 628 if (includingFields && containingClass.hidden) { 629 field.hidden = true 630 } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) { 631 // See explanation in visitMethod 632 field.hidden = true 633 } 634 if (containingClass.docOnly) { 635 field.docOnly = true 636 } 637 if (containingClass.removed) { 638 field.removed = true 639 } 640 } 641 } 642 643 private fun ensureParentVisible(item: Item) { 644 val parent = item.parent() ?: return 645 if (parent.hidden && item.modifiers.hasShowSingleAnnotation()) { 646 val annotation = item.modifiers.annotations().find { 647 options.showSingleAnnotations.matches(it) 648 } ?: options.showSingleAnnotations.firstQualifiedName() 649 reporter.report( 650 Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS, item, 651 "Attempting to unhide ${item.describe()}, but surrounding ${parent.describe()} is " + 652 "hidden and should also be annotated with $annotation" 653 ) 654 } 655 } 656 }) 657 } 658 659 private fun checkSystemPermissions(method: MethodItem) { 660 if (method.isImplicitConstructor()) { // Don't warn on non-source elements like implicit default constructors 661 return 662 } 663 664 val annotation = method.modifiers.findAnnotation(ANDROID_REQUIRES_PERMISSION) 665 var hasAnnotation = false 666 667 if (annotation != null) { 668 hasAnnotation = true 669 for (attribute in annotation.attributes()) { 670 var values: List<AnnotationAttributeValue>? = null 671 var any = false 672 when (attribute.name) { 673 "value", "allOf" -> { 674 values = attribute.leafValues() 675 } 676 "anyOf" -> { 677 any = true 678 values = attribute.leafValues() 679 } 680 } 681 682 values ?: continue 683 684 val system = ArrayList<String>() 685 val nonSystem = ArrayList<String>() 686 val missing = ArrayList<String>() 687 for (value in values) { 688 val perm = (value.value() ?: value.toSource()).toString() 689 val level = codebase.getPermissionLevel(perm) 690 if (level == null) { 691 if (any) { 692 missing.add(perm) 693 continue 694 } 695 696 reporter.report( 697 Issues.REQUIRES_PERMISSION, method, 698 "Permission '$perm' is not defined by manifest ${codebase.manifest}." 699 ) 700 continue 701 } 702 if (level.contains("normal") || level.contains("dangerous") || 703 level.contains("ephemeral") 704 ) { 705 nonSystem.add(perm) 706 } else { 707 system.add(perm) 708 } 709 } 710 if (any && missing.size == values.size) { 711 reporter.report( 712 Issues.REQUIRES_PERMISSION, method, 713 "None of the permissions ${missing.joinToString()} are defined by manifest " + 714 "${codebase.manifest}." 715 ) 716 } 717 718 if (system.isEmpty() && nonSystem.isEmpty()) { 719 hasAnnotation = false 720 } else if (any && !nonSystem.isEmpty() || !any && system.isEmpty()) { 721 reporter.report( 722 Issues.REQUIRES_PERMISSION, method, "Method '" + method.name() + 723 "' must be protected with a system permission; it currently" + 724 " allows non-system callers holding " + nonSystem.toString() 725 ) 726 } 727 } 728 } 729 730 if (!hasAnnotation) { 731 reporter.report( 732 Issues.REQUIRES_PERMISSION, method, "Method '" + method.name() + 733 "' must be protected with a system permission." 734 ) 735 } 736 } 737 738 fun performChecks() { 739 if (codebase.trustedApi()) { 740 // The codebase is already an API; no consistency checks to be performed 741 return 742 } 743 744 val checkSystemApi = !reporter.isSuppressed(Issues.REQUIRES_PERMISSION) && 745 options.showAnnotations.matches(ANDROID_SYSTEM_API) && options.manifest != null 746 val checkHiddenShowAnnotations = !reporter.isSuppressed(Issues.UNHIDDEN_SYSTEM_API) && 747 options.showAnnotations.isNotEmpty() 748 749 packages.accept(object : ApiVisitor() { 750 override fun visitParameter(parameter: ParameterItem) { 751 checkTypeReferencesHidden(parameter, parameter.type()) 752 } 753 754 override fun visitItem(item: Item) { 755 if (item.deprecated && !item.documentation.contains("@deprecated") && 756 // Don't warn about this in Kotlin; the Kotlin deprecation annotation includes deprecation 757 // messages (unlike java.lang.Deprecated which has no attributes). Instead, these 758 // are added to the documentation by the [DocAnalyzer]. 759 !item.isKotlin() 760 ) { 761 reporter.report( 762 Issues.DEPRECATION_MISMATCH, item, 763 "${item.toString().capitalize()}: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match" 764 ) 765 // TODO: Check opposite (doc tag but no annotation) 766 } 767 768 if (checkHiddenShowAnnotations && 769 item.hasShowAnnotation() && 770 !item.documentation.contains("@hide") && 771 !item.modifiers.hasShowSingleAnnotation() 772 ) { 773 val annotationName = (item.modifiers.annotations().firstOrNull { annotation -> 774 options.showAnnotations.matches(annotation) 775 }?.qualifiedName() ?: options.showAnnotations.firstQualifiedName()).removePrefix(ANDROID_ANNOTATION_PREFIX) 776 reporter.report( 777 Issues.UNHIDDEN_SYSTEM_API, item, 778 "@$annotationName APIs must also be marked @hide: ${item.describe()}" 779 ) 780 } 781 } 782 783 override fun visitClass(cls: ClassItem) { 784 // Propagate @Deprecated flags down from classes into inner classes, if configured. 785 // Done here rather than in the analyzer which propagates visibility, since we want to do it 786 // after warning 787 val containingClass = cls.containingClass() 788 if (containingClass != null && containingClass.deprecated && compatibility.propagateDeprecatedInnerClasses) { 789 cls.deprecated = true 790 } 791 792 if (checkSystemApi) { 793 // Look for Android @SystemApi exposed outside the normal SDK; we require 794 // that they're protected with a system permission. 795 // Also flag @SystemApi apis not annotated with @hide. 796 797 // This class is a system service if it's annotated with @SystemService, 798 // or if it's android.content.pm.PackageManager 799 if (cls.modifiers.isAnnotatedWith("android.annotation.SystemService") || 800 cls.qualifiedName() == "android.content.pm.PackageManager" 801 ) { 802 // Check permissions on system services 803 for (method in cls.filteredMethods(filterEmit)) { 804 checkSystemPermissions(method) 805 } 806 } 807 } 808 } 809 810 override fun visitField(field: FieldItem) { 811 val containingClass = field.containingClass() 812 if (containingClass.deprecated && compatibility.propagateDeprecatedMembers) { 813 field.deprecated = true 814 } 815 816 checkTypeReferencesHidden(field, field.type()) 817 } 818 819 override fun visitMethod(method: MethodItem) { 820 if (!method.isConstructor()) { 821 checkTypeReferencesHidden( 822 method, 823 method.returnType()!! 824 ) // returnType is nullable only for constructors 825 } 826 827 val containingClass = method.containingClass() 828 if (containingClass.deprecated && compatibility.propagateDeprecatedMembers) { 829 method.deprecated = true 830 } 831 832 // Make sure we don't annotate findViewById & getSystemService as @Nullable. 833 // See for example 68914170. 834 val name = method.name() 835 if ((name == "findViewById" || name == "getSystemService") && method.parameters().size == 1 && 836 method.modifiers.isNullable() 837 ) { 838 reporter.report( 839 Issues.EXPECTED_PLATFORM_TYPE, method, 840 "$method should not be annotated @Nullable; it should be left unspecified to make it a platform type" 841 ) 842 val annotation = method.modifiers.annotations().find { it.isNullable() } 843 annotation?.let { 844 method.mutableModifiers().removeAnnotation(it) 845 // Have to also clear the annotation out of the return type itself, if it's a type 846 // use annotation 847 method.returnType()?.scrubAnnotations() 848 } 849 } 850 } 851 852 private fun checkTypeReferencesHidden(item: Item, type: TypeItem) { 853 if (type.primitive) { 854 return 855 } 856 857 val cls = type.asClass() 858 859 // Don't flag type parameters like T 860 if (cls?.isTypeParameter == true) { 861 return 862 } 863 864 // class may be null for things like array types and ellipsis types, 865 // but iterating through the type argument classes below will find and 866 // check the component class 867 if (cls != null && !filterReference.test(cls) && !cls.isFromClassPath()) { 868 reporter.report( 869 Issues.HIDDEN_TYPE_PARAMETER, item, 870 "${item.toString().capitalize()} references hidden type $type." 871 ) 872 } 873 874 type.typeArgumentClasses() 875 .filter { it != cls } 876 .forEach { checkTypeReferencesHidden(item, it) } 877 } 878 879 private fun checkTypeReferencesHidden(item: Item, cls: ClassItem) { 880 if (!filterReference.test(cls)) { 881 if (!cls.isFromClassPath()) { 882 reporter.report( 883 Issues.HIDDEN_TYPE_PARAMETER, item, 884 "${item.toString().capitalize()} references hidden type $cls." 885 ) 886 } 887 } else { 888 cls.typeArgumentClasses() 889 .filter { it != cls } 890 .forEach { checkTypeReferencesHidden(item, it) } 891 } 892 } 893 }) 894 } 895 896 fun handleStripping() { 897 // TODO: Switch to visitor iteration 898 // val stubPackages = options.stubPackages 899 val stubImportPackages = options.stubImportPackages 900 handleStripping(stubImportPackages) 901 } 902 903 private fun handleStripping(stubImportPackages: Set<String>) { 904 val notStrippable = HashSet<ClassItem>(5000) 905 906 val filter = ApiPredicate(ignoreShown = true) 907 908 // If a class is public or protected, not hidden, not imported and marked as included, 909 // then we can't strip it 910 val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList() 911 allTopLevelClasses 912 .filter { it.checkLevel() && it.emit && !it.hidden() } 913 .forEach { 914 cantStripThis(it, filter, notStrippable, stubImportPackages, it, "self") 915 } 916 917 // complain about anything that looks includeable but is not supposed to 918 // be written, e.g. hidden things 919 for (cl in notStrippable) { 920 if (!cl.isHiddenOrRemoved()) { 921 for (m in cl.methods()) { 922 if (!m.checkLevel()) { 923 continue 924 } 925 if (m.isHiddenOrRemoved()) { 926 reporter.report( 927 Issues.UNAVAILABLE_SYMBOL, m, 928 "Reference to unavailable method " + m.name() 929 ) 930 } else if (m.deprecated) { 931 // don't bother reporting deprecated methods 932 // unless they are public 933 reporter.report( 934 Issues.DEPRECATED, m, "Method " + cl.qualifiedName() + "." + 935 m.name() + " is deprecated" 936 ) 937 } 938 939 val returnType = m.returnType() 940 if (!m.deprecated && !cl.deprecated && returnType != null && returnType.asClass()?.deprecated == true) { 941 reporter.report( 942 Issues.REFERENCES_DEPRECATED, m, 943 "Return type of deprecated type $returnType in ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated" 944 ) 945 } 946 947 var hiddenClass = findHiddenClasses(returnType, stubImportPackages) 948 if (hiddenClass != null && !hiddenClass.isFromClassPath()) { 949 if (hiddenClass.qualifiedName() == returnType?.asClass()?.qualifiedName()) { 950 // Return type is hidden 951 reporter.report( 952 Issues.UNAVAILABLE_SYMBOL, m, 953 "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " + 954 "type ${hiddenClass.simpleName()}" 955 ) 956 } else { 957 // Return type contains a generic parameter 958 reporter.report( 959 Issues.HIDDEN_TYPE_PARAMETER, m, 960 "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " + 961 "type ${hiddenClass.simpleName()} as a type parameter" 962 ) 963 } 964 } 965 966 for (p in m.parameters()) { 967 val t = p.type() 968 if (!t.primitive) { 969 if (!m.deprecated && !cl.deprecated && t.asClass()?.deprecated == true) { 970 reporter.report( 971 Issues.REFERENCES_DEPRECATED, m, 972 "Parameter of deprecated type $t in ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated" 973 ) 974 } 975 976 hiddenClass = findHiddenClasses(t, stubImportPackages) 977 if (hiddenClass != null && !hiddenClass.isFromClassPath()) { 978 if (hiddenClass.qualifiedName() == t.asClass()?.qualifiedName()) { 979 // Parameter type is hidden 980 reporter.report( 981 Issues.UNAVAILABLE_SYMBOL, m, 982 "Parameter of unavailable type $t in ${cl.qualifiedName()}.${m.name()}()" 983 ) 984 } else { 985 // Parameter type contains a generic parameter 986 reporter.report( 987 Issues.HIDDEN_TYPE_PARAMETER, m, 988 "Parameter uses type parameter of unavailable type $t in ${cl.qualifiedName()}.${m.name()}()" 989 ) 990 } 991 } 992 } 993 } 994 995 val t = m.returnType() 996 if (t != null && !t.primitive && !m.deprecated && !cl.deprecated && t.asClass()?.deprecated == true) { 997 reporter.report( 998 Issues.REFERENCES_DEPRECATED, m, 999 "Returning deprecated type $t from ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated" 1000 ) 1001 } 1002 } 1003 1004 if (!cl.deprecated) { 1005 val s = cl.superClass() 1006 if (s?.deprecated == true) { 1007 reporter.report( 1008 Issues.EXTENDS_DEPRECATED, cl, 1009 "Extending deprecated super class $s from ${cl.qualifiedName()}: this class should also be deprecated" 1010 ) 1011 } 1012 1013 for (t in cl.interfaceTypes()) { 1014 if (t.asClass()?.deprecated == true) { 1015 reporter.report( 1016 Issues.EXTENDS_DEPRECATED, cl, 1017 "Implementing interface of deprecated type $t in ${cl.qualifiedName()}: this class should also be deprecated" 1018 ) 1019 } 1020 } 1021 } 1022 } else if (cl.deprecated) { 1023 // not hidden, but deprecated 1024 reporter.report(Issues.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated") 1025 } else if (reporter.isSuppressed(Issues.REFERENCES_HIDDEN, cl)) { 1026 // If we're not reporting hidden references, bring the type back 1027 // Bring this class back 1028 cl.hidden = false 1029 cl.removed = false 1030 cl.notStrippable = true 1031 } 1032 } 1033 } 1034 1035 private fun cantStripThis( 1036 cl: ClassItem, 1037 filter: Predicate<Item>, 1038 notStrippable: MutableSet<ClassItem>, 1039 stubImportPackages: Set<String>?, 1040 from: Item, 1041 usage: String 1042 ) { 1043 if (stubImportPackages != null && stubImportPackages.contains(cl.containingPackage().qualifiedName())) { 1044 // if the package is imported then it does not need stubbing. 1045 return 1046 } 1047 1048 if (cl.isFromClassPath()) { 1049 return 1050 } 1051 1052 if ((cl.isHiddenOrRemoved() || cl.isPackagePrivate && !cl.checkLevel()) && !cl.isTypeParameter) { 1053 reporter.report( 1054 Issues.REFERENCES_HIDDEN, from, 1055 "Class ${cl.qualifiedName()} is ${if (cl.isHiddenOrRemoved()) "hidden" else "not public"} but was referenced ($usage) from public ${from.describe( 1056 false 1057 )}" 1058 ) 1059 cl.notStrippable = true 1060 } 1061 1062 if (!notStrippable.add(cl)) { 1063 // slight optimization: if it already contains cl, it already contains 1064 // all of cl's parents 1065 return 1066 } 1067 1068 // cant strip any public fields or their generics 1069 for (field in cl.fields()) { 1070 if (!filter.test(field)) { 1071 continue 1072 } 1073 val fieldType = field.type() 1074 if (!fieldType.primitive) { 1075 val typeClass = fieldType.asClass() 1076 if (typeClass != null) { 1077 cantStripThis(typeClass, filter, notStrippable, stubImportPackages, field, "as field type") 1078 } 1079 for (cls in fieldType.typeArgumentClasses()) { 1080 if (cls == typeClass) { 1081 continue 1082 } 1083 cantStripThis(cls, filter, notStrippable, stubImportPackages, field, "as field type argument class") 1084 } 1085 } 1086 } 1087 // cant strip any of the type's generics 1088 for (cls in cl.typeArgumentClasses()) { 1089 cantStripThis(cls, filter, notStrippable, stubImportPackages, cl, "as type argument") 1090 } 1091 // cant strip any of the annotation elements 1092 // cantStripThis(cl.annotationElements(), notStrippable); 1093 // take care of methods 1094 cantStripThis(cl.methods(), filter, notStrippable, stubImportPackages) 1095 cantStripThis(cl.constructors(), filter, notStrippable, stubImportPackages) 1096 // blow the outer class open if this is an inner class 1097 val containingClass = cl.containingClass() 1098 if (containingClass != null) { 1099 cantStripThis(containingClass, filter, notStrippable, stubImportPackages, cl, "as containing class") 1100 } 1101 // blow open super class and interfaces 1102 // TODO: Consider using val superClass = cl.filteredSuperclass(filter) 1103 val superClass = cl.superClass() 1104 if (superClass != null) { 1105 if (superClass.isHiddenOrRemoved()) { 1106 // cl is a public class declared as extending a hidden superclass. 1107 // this is not a desired practice but it's happened, so we deal 1108 // with it by finding the first super class which passes checkLevel for purposes of 1109 // generating the doc & stub information, and proceeding normally. 1110 if (!superClass.isFromClassPath()) { 1111 reporter.report( 1112 Issues.HIDDEN_SUPERCLASS, cl, "Public class " + cl.qualifiedName() + 1113 " stripped of unavailable superclass " + superClass.qualifiedName() 1114 ) 1115 } 1116 } else { 1117 // doclava would also mark the package private super classes as unhidden, but that's not 1118 // right (this was just done for its stub handling) 1119 // cantStripThis(superClass, filter, notStrippable, stubImportPackages, cl, "as super class") 1120 1121 if (superClass.isPrivate && !superClass.isFromClassPath()) { 1122 reporter.report( 1123 Issues.PRIVATE_SUPERCLASS, cl, "Public class " + 1124 cl.qualifiedName() + " extends private class " + superClass.qualifiedName() 1125 ) 1126 } 1127 } 1128 } 1129 } 1130 1131 private fun cantStripThis( 1132 methods: List<MethodItem>, 1133 filter: Predicate<Item>, 1134 notStrippable: MutableSet<ClassItem>, 1135 stubImportPackages: Set<String>? 1136 ) { 1137 // for each method, blow open the parameters, throws and return types. also blow open their 1138 // generics 1139 for (method in methods) { 1140 if (!filter.test(method)) { 1141 continue 1142 } 1143 for (typeParameterClass in method.typeArgumentClasses()) { 1144 cantStripThis( 1145 typeParameterClass, 1146 filter, 1147 notStrippable, 1148 stubImportPackages, 1149 method, 1150 "as type parameter" 1151 ) 1152 } 1153 for (parameter in method.parameters()) { 1154 for (parameterTypeClass in parameter.type().typeArgumentClasses()) { 1155 cantStripThis( 1156 parameterTypeClass, 1157 filter, 1158 notStrippable, 1159 stubImportPackages, 1160 parameter, 1161 "as parameter type" 1162 ) 1163 for (tcl in parameter.type().typeArgumentClasses()) { 1164 if (tcl == parameterTypeClass) { 1165 continue 1166 } 1167 if (tcl.isHiddenOrRemoved()) { 1168 reporter.report( 1169 Issues.UNAVAILABLE_SYMBOL, method, 1170 "Parameter of hidden type ${tcl.fullName()} " + 1171 "in ${method.containingClass().qualifiedName()}.${method.name()}()" 1172 ) 1173 } else { 1174 cantStripThis( 1175 tcl, 1176 filter, 1177 notStrippable, 1178 stubImportPackages, 1179 parameter, 1180 "as type parameter" 1181 ) 1182 } 1183 } 1184 } 1185 } 1186 for (thrown in method.throwsTypes()) { 1187 cantStripThis(thrown, filter, notStrippable, stubImportPackages, method, "as exception") 1188 } 1189 val returnType = method.returnType() 1190 if (returnType != null && !returnType.primitive) { 1191 val returnTypeClass = returnType.asClass() 1192 if (returnTypeClass != null) { 1193 cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages, method, "as return type") 1194 for (tyItem in returnType.typeArgumentClasses()) { 1195 if (tyItem == returnTypeClass) { 1196 continue 1197 } 1198 cantStripThis( 1199 tyItem, 1200 filter, 1201 notStrippable, 1202 stubImportPackages, 1203 method, 1204 "as return type parameter" 1205 ) 1206 } 1207 } 1208 } 1209 } 1210 } 1211 1212 /** 1213 * Find references to hidden classes. 1214 * 1215 * This finds hidden classes that are used by public parts of the API in order to ensure the 1216 * API is self consistent and does not reference classes that are not included in 1217 * the stubs. Any such references cause an error to be reported. 1218 * 1219 * A reference to an imported class is not treated as an error, even though imported classes 1220 * are hidden from the stub generation. That is because imported classes are, by definition, 1221 * excluded from the set of classes for which stubs are required. 1222 * 1223 * @param ti the type information to examine for references to hidden classes. 1224 * @param stubImportPackages the possibly null set of imported package names. 1225 * @return a reference to a hidden class or null if there are none 1226 */ 1227 private fun findHiddenClasses(ti: TypeItem?, stubImportPackages: Set<String>?): ClassItem? { 1228 ti ?: return null 1229 val ci = ti.asClass() ?: return null 1230 return findHiddenClasses(ci, stubImportPackages) 1231 } 1232 1233 private fun findHiddenClasses(ci: ClassItem, stubImportPackages: Set<String>?): ClassItem? { 1234 if (stubImportPackages != null && stubImportPackages.contains(ci.containingPackage().qualifiedName())) { 1235 return null 1236 } 1237 if (ci.isHiddenOrRemoved()) return ci 1238 for (tii in ci.toType().typeArgumentClasses()) { 1239 // Avoid infinite recursion in the case of Foo<T extends Foo> 1240 if (tii != ci) { 1241 val hiddenClass = findHiddenClasses(tii, stubImportPackages) 1242 if (hiddenClass != null) return hiddenClass 1243 } 1244 } 1245 return null 1246 } 1247 } 1248