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