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