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