• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.apilevels.internalDesc
20 import com.android.tools.metalava.manifest.Manifest
21 import com.android.tools.metalava.manifest.emptyManifest
22 import com.android.tools.metalava.model.ANDROIDX_REQUIRES_PERMISSION
23 import com.android.tools.metalava.model.ANDROID_ANNOTATION_PREFIX
24 import com.android.tools.metalava.model.ANDROID_DEPRECATED_FOR_SDK
25 import com.android.tools.metalava.model.ANDROID_SYSTEM_API
26 import com.android.tools.metalava.model.ANNOTATION_ATTR_VALUE
27 import com.android.tools.metalava.model.AnnotationAttributeValue
28 import com.android.tools.metalava.model.AnnotationItem
29 import com.android.tools.metalava.model.BaseItemVisitor
30 import com.android.tools.metalava.model.BaseTypeVisitor
31 import com.android.tools.metalava.model.CallableItem
32 import com.android.tools.metalava.model.ClassItem
33 import com.android.tools.metalava.model.ClassOrigin
34 import com.android.tools.metalava.model.ClassTypeItem
35 import com.android.tools.metalava.model.Codebase
36 import com.android.tools.metalava.model.FieldItem
37 import com.android.tools.metalava.model.FilterPredicate
38 import com.android.tools.metalava.model.Item
39 import com.android.tools.metalava.model.JAVA_LANG_DEPRECATED
40 import com.android.tools.metalava.model.MethodItem
41 import com.android.tools.metalava.model.PackageList
42 import com.android.tools.metalava.model.ParameterItem
43 import com.android.tools.metalava.model.PropertyItem
44 import com.android.tools.metalava.model.SelectableItem
45 import com.android.tools.metalava.model.TypeItem
46 import com.android.tools.metalava.model.TypeParameterList
47 import com.android.tools.metalava.model.VariableTypeItem
48 import com.android.tools.metalava.model.annotation.AnnotationFilter
49 import com.android.tools.metalava.model.source.SourceParser
50 import com.android.tools.metalava.model.visitors.ApiPredicate
51 import com.android.tools.metalava.model.visitors.ApiVisitor
52 import com.android.tools.metalava.reporter.Issues
53 import com.android.tools.metalava.reporter.Reporter
54 import java.io.File
55 import java.util.Locale
56 
57 /**
58  * The [ApiAnalyzer] is responsible for walking over the various classes and members and compute
59  * visibility etc. of the APIs
60  */
61 class ApiAnalyzer(
62     private val sourceParser: SourceParser,
63     /** The code to analyze */
64     private val codebase: Codebase,
65     private val reporter: Reporter,
66     private val config: Config = Config(),
67 ) {
68 
69     data class Config(
70         val manifest: Manifest = emptyManifest,
71 
72         /** Packages to exclude/hide */
73         val hidePackages: List<String> = emptyList(),
74 
75         /**
76          * Packages that we should skip generating even if not hidden; typically only used by tests
77          */
78         val skipEmitPackages: List<String> = emptyList(),
79 
80         /**
81          * External annotation files that contain non-inclusion annotations which will appear in the
82          * generated API.
83          *
84          * These will be merged into the codebase.
85          */
86         val mergeQualifierAnnotations: List<File> = emptyList(),
87 
88         /**
89          * External annotation files that contain annotations which affect inclusion of items in the
90          * API.
91          *
92          * These will be merged into the codebase.
93          */
94         val mergeInclusionAnnotations: List<File> = emptyList(),
95 
96         /** The filter for all the show annotations. */
97         val allShowAnnotations: AnnotationFilter = AnnotationFilter.emptyFilter(),
98 
99         /** Configuration for any [ApiPredicate] instances this needs to create. */
100         val apiPredicateConfig: ApiPredicate.Config = ApiPredicate.Config()
101     )
102 
103     /** All packages in the API */
104     private val packages: PackageList = codebase.getPackages()
105 
106     fun computeApi() {
107         if (codebase.trustedApi()) {
108             // The codebase is already an API; no consistency checks to be performed
109             return
110         }
111 
112         skipEmitPackages()
113         // Suppress kotlin file facade classes with no public api
114         hideEmptyKotlinFileFacadeClasses()
115 
116         // Propagate visibility down into individual elements -- if a class is hidden,
117         // then the methods and fields are hidden etc
118         propagateHiddenRemovedAndDocOnly()
119     }
120 
121     fun generateInheritedStubs(filterEmit: FilterPredicate, filterReference: FilterPredicate) {
122         // When analyzing libraries we may discover some new classes during traversal; these aren't
123         // part of the API but may be super classes or interfaces; these will then be added into the
124         // package class lists, which could trigger a concurrent modification, so create a snapshot
125         // of the class list and iterate over it:
126         val allClasses = packages.allClasses().toList()
127 
128         val visited = mutableSetOf<ClassItem>()
129         allClasses.forEach { generateInheritedStubs(it, filterEmit, filterReference, visited) }
130     }
131 
132     private fun generateInheritedStubs(
133         cls: ClassItem,
134         filterEmit: FilterPredicate,
135         filterReference: FilterPredicate,
136         visited: MutableSet<ClassItem>,
137     ) {
138         // If it is not a class, i.e. an interface, etc., then return.
139         if (!cls.isClass()) return
140 
141         // If already visited this class then ignore it. Otherwise, remember that this was visited.
142         if (cls in visited) return
143         visited += cls
144 
145         // If it has no super class then ignore it.
146         val superClass = cls.superClass() ?: return
147 
148         // If the class is not going to be emitted then do not inherit any methods into it.
149         if (!filterEmit.test(cls)) return
150 
151         // Make sure that the super class has inherited the stubs and interfaces.
152         generateInheritedStubs(superClass, filterEmit, filterReference, visited)
153 
154         val allSuperClasses = cls.allSuperClasses()
155         val hiddenSuperClasses =
156             allSuperClasses.filter { !filterReference.test(it) && !it.isJavaLangObject() }
157 
158         if (hiddenSuperClasses.none()) { // not missing any implementation methods
159             return
160         }
161 
162         addInheritedStubsFrom(cls, hiddenSuperClasses, allSuperClasses, filterEmit, filterReference)
163         addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference)
164     }
165 
166     private fun addInheritedInterfacesFrom(
167         cls: ClassItem,
168         hiddenSuperClasses: Sequence<ClassItem>,
169         filterReference: FilterPredicate
170     ) {
171         var interfaceTypes: MutableList<ClassTypeItem>? = null
172         var interfaceTypeClasses: MutableList<ClassItem>? = null
173         for (hiddenSuperClass in hiddenSuperClasses) {
174             for (hiddenInterface in hiddenSuperClass.interfaceTypes()) {
175                 val hiddenInterfaceClass = hiddenInterface.asClass()
176                 if (filterReference.test(hiddenInterfaceClass ?: continue)) {
177                     if (interfaceTypes == null) {
178                         interfaceTypes = cls.interfaceTypes().toMutableList()
179                         interfaceTypeClasses =
180                             interfaceTypes.mapNotNull { it.asClass() }.toMutableList()
181                         if (cls.isInterface()) {
182                             cls.superClass()?.let { interfaceTypeClasses.add(it) }
183                         }
184                         cls.setInterfaceTypes(interfaceTypes)
185                     }
186                     if (interfaceTypeClasses!!.any { it == hiddenInterfaceClass }) {
187                         continue
188                     }
189 
190                     interfaceTypeClasses.add(hiddenInterfaceClass)
191 
192                     if (hiddenInterfaceClass.hasTypeVariables()) {
193                         val mapping = cls.mapTypeVariables(hiddenSuperClass)
194                         if (mapping.isNotEmpty()) {
195                             val mappedType = hiddenInterface.convertType(mapping)
196                             interfaceTypes.add(mappedType)
197                             continue
198                         }
199                     }
200 
201                     interfaceTypes.add(hiddenInterface)
202                 }
203             }
204         }
205     }
206 
207     private fun addInheritedStubsFrom(
208         cls: ClassItem,
209         hiddenSuperClasses: Sequence<ClassItem>,
210         superClasses: Sequence<ClassItem>,
211         filterEmit: FilterPredicate,
212         filterReference: FilterPredicate
213     ) {
214         // Also generate stubs for any methods we would have inherited from abstract parents
215         // All methods from super classes that (1) aren't overridden in this class already, and
216         // (2) are overriding some method that is in a public interface accessible from this class.
217         val interfaces: Set<TypeItem> = cls.allInterfaceTypes(filterReference).toSet()
218 
219         // Note that we can't just call method.superMethods() to and see whether any of their
220         // containing classes are among our target APIs because it's possible that the super class
221         // doesn't actually implement the interface, but still provides a matching signature for the
222         // interface. Instead, we'll look through all of our interface methods and look for
223         // potential overrides.
224         val inheritableMethods = MethodItemSet()
225         for (interfaceType in interfaces) {
226             val interfaceClass = interfaceType.asClass() ?: continue
227             for (method in interfaceClass.methods()) {
228                 inheritableMethods.add(method)
229             }
230         }
231 
232         // Also add in any abstract methods from public super classes
233         val publicSuperClasses =
234             superClasses.filter { filterEmit.test(it) && !it.isJavaLangObject() }
235         for (superClass in publicSuperClasses) {
236             for (method in superClass.methods()) {
237                 if (!method.modifiers.isAbstract() || !method.modifiers.isPublicOrProtected()) {
238                     continue
239                 }
240                 inheritableMethods.add(method)
241             }
242         }
243 
244         // Also add in any concrete public methods from hidden super classes
245         for (superClass in hiddenSuperClasses) {
246             // Determine if there is a non-hidden class between the superClass and this class.
247             // If non-hidden classes are found, don't include the methods for this hiddenSuperClass,
248             // as it will already have been included in a previous super class
249             val includeHiddenSuperClassMethods =
250                 !cls.allSuperClasses()
251                     // Search from this class up to, but not including the superClass.
252                     .takeWhile { currentClass -> currentClass != superClass }
253                     // Find any class that is not hidden.
254                     .any { currentClass -> !hiddenSuperClasses.contains(currentClass) }
255 
256             if (!includeHiddenSuperClassMethods) {
257                 continue
258             }
259 
260             for (method in superClass.methods()) {
261                 if (method.modifiers.isAbstract() || !method.modifiers.isPublic()) {
262                     continue
263                 }
264 
265                 if (method.hasHiddenType(filterReference)) {
266                     continue
267                 }
268 
269                 inheritableMethods.add(method)
270             }
271         }
272 
273         // Find all methods that are inherited from these classes into our class (making sure that
274         // we don't have duplicates, e.g. a method defined by one inherited class and then
275         // overridden by another closer one). map from method name to super methods overriding our
276         // interfaces
277         val inheritedMethods = MethodItemSet()
278 
279         for (superClass in hiddenSuperClasses) {
280             for (method in superClass.methods()) {
281                 val modifiers = method.modifiers
282                 if (!modifiers.isPrivate() && !modifiers.isAbstract()) {
283                     if (inheritableMethods.containsMatchingMethod(method)) {
284                         inheritedMethods.add(method)
285                     }
286                 }
287             }
288         }
289 
290         // Remove any methods that are overriding any of our existing methods
291         for (method in cls.methods()) {
292             inheritedMethods.removeMatchingMethods(method)
293         }
294 
295         // Next remove any overrides among the remaining super methods (e.g. one method from a
296         // hidden parent is overriding another method from a more distant hidden parent).
297         inheritedMethods.values.forEach { methods ->
298             if (methods.size >= 2) {
299                 for (candidate in ArrayList(methods)) {
300                     for (superMethod in candidate.allSuperMethods()) {
301                         methods.remove(superMethod)
302                     }
303                 }
304             }
305         }
306 
307         // Add all the existing methods in the class to the set of existing methods.
308         val existingMethods = MethodItemSet()
309         for (method in cls.methods()) {
310             existingMethods.add(method)
311         }
312 
313         // We're now left with concrete methods in hidden parents that are implementing methods in
314         // public interfaces that are listed in this class. Create stubs for them:
315         inheritedMethods.values.flatten().forEach {
316             // Copy the method from the hidden class that is not part of the API into the class that
317             // is part of the API.
318             val method = it.duplicate(cls)
319             /* Insert comment marker: This is useful for debugging purposes but doesn't
320                belong in the stub
321             method.documentation = "// Inlined stub from hidden parent class ${it.containingClass().qualifiedName()}\n" +
322                     method.documentation
323              */
324 
325             // If we already have an override of this method, do not add it to the methods list
326             if (existingMethods.containsMatchingMethod(method)) {
327                 return@forEach
328             }
329 
330             val runtimeDesc = it.internalDesc()
331             val stubDesc = method.internalDesc()
332             if (filterEmit.test(method) && runtimeDesc != stubDesc) {
333                 // This is problematic primarily for the platform where we use stubs, and the
334                 // generated method in the android.jar won't actually exist at runtime.
335                 // While we don't use stubs in AndroidX, this can still cause compat issues because
336                 // the current.txt (which will show the equivalent of stubDesc) won't actually match
337                 // the ABI of the library (because call sites will reference runtimeDesc).
338                 reporter.report(
339                     Issues.INHERIT_CHANGES_SIGNATURE,
340                     it,
341                     "Explicitly override $it in $cls, or hide it in ${it.containingClass()};" +
342                         " it cannot be implicitly inherited as API from the hidden super class" +
343                         " because that would change its erased signature from $runtimeDesc to" +
344                         " $stubDesc, and cause failures at runtime.",
345                 )
346             }
347 
348             cls.addMethod(method)
349 
350             // Make sure that the same method is not added from multiple super classes.
351             existingMethods.add(method)
352         }
353     }
354 
355     /** Apply package filters listed in [Options.skipEmitPackages] */
356     private fun skipEmitPackages() {
357         for (pkgName in config.skipEmitPackages) {
358             val pkg = codebase.findPackage(pkgName) ?: continue
359             pkg.emit = false
360         }
361     }
362 
363     /** If a file facade class has no public members, don't add it to the api */
364     private fun hideEmptyKotlinFileFacadeClasses() {
365         codebase.getPackages().allClasses().forEach { cls ->
366             if (
367                 cls.isFileFacade() &&
368                     // a facade class needs to be emitted if it has any top-level fun/prop to emit
369                     cls.members().none { member ->
370                         // a member needs to be emitted if
371                         //  1) it doesn't have a hide annotation;
372                         //  2) it is either public or has a show annotation;
373                         //  3) it is not `expect`
374                         !member.hasHideAnnotation() &&
375                             (member.isPublic || member.hasShowAnnotation()) &&
376                             !member.modifiers.isExpect()
377                     }
378             ) {
379                 cls.emit = false
380             }
381         }
382     }
383 
384     /**
385      * Merge in external qualifier annotations (i.e. ones intended to be included in the API written
386      * from all configured sources).
387      */
388     fun mergeExternalQualifierAnnotations() {
389         val mergeQualifierAnnotations = config.mergeQualifierAnnotations
390         if (mergeQualifierAnnotations.isNotEmpty()) {
391             AnnotationsMerger(sourceParser, codebase, reporter)
392                 .mergeQualifierAnnotationsFromFiles(mergeQualifierAnnotations)
393         }
394     }
395 
396     /** Merge in external show/hide annotations from all configured sources */
397     fun mergeExternalInclusionAnnotations() {
398         val mergeInclusionAnnotations = config.mergeInclusionAnnotations
399         if (mergeInclusionAnnotations.isNotEmpty()) {
400             AnnotationsMerger(sourceParser, codebase, reporter)
401                 .mergeInclusionAnnotationsFromFiles(mergeInclusionAnnotations)
402         }
403     }
404 
405     /**
406      * Propagate the hidden flag down into individual elements -- if a class is hidden, then the
407      * methods and fields are hidden etc
408      */
409     private fun propagateHiddenRemovedAndDocOnly() {
410         // Create a visitor to propagate hidden and docOnly from the containing package onto the top
411         // level classes and then propagate them, and removed status, down onto the nested classes
412         // and members.
413         val visitor =
414             object :
415                 BaseItemVisitor(
416                     preserveClassNesting = true,
417                     // Only SelectableItems can have variantSelectors.
418                     visitParameterItems = false,
419                 ) {
420                 override fun visitSelectableItem(item: SelectableItem) {
421                     item.variantSelectors.inheritInto()
422                 }
423             }
424 
425         codebase.accept(visitor)
426     }
427 
428     private fun checkSystemPermissions(method: MethodItem) {
429         val annotation = method.modifiers.findAnnotation(ANDROIDX_REQUIRES_PERMISSION)
430         var hasAnnotation = false
431 
432         if (annotation != null) {
433             hasAnnotation = true
434             for (attribute in annotation.attributes) {
435                 var values: List<AnnotationAttributeValue>? = null
436                 var any = false
437                 when (attribute.name) {
438                     "value",
439                     "allOf" -> {
440                         values = attribute.leafValues()
441                     }
442                     "anyOf" -> {
443                         any = true
444                         values = attribute.leafValues()
445                     }
446                 }
447 
448                 values ?: continue
449 
450                 val system = ArrayList<String>()
451                 val nonSystem = ArrayList<String>()
452                 val missing = ArrayList<String>()
453                 for (value in values) {
454                     val perm = (value.value() ?: value.toSource()).toString()
455                     val level = config.manifest.getPermissionLevel(perm)
456                     if (level == null) {
457                         if (any) {
458                             missing.add(perm)
459                             continue
460                         }
461 
462                         reporter.report(
463                             Issues.REQUIRES_PERMISSION,
464                             method,
465                             "Permission '$perm' is not defined by manifest ${config.manifest}."
466                         )
467                         continue
468                     }
469                     if (
470                         level.contains("normal") ||
471                             level.contains("dangerous") ||
472                             level.contains("ephemeral")
473                     ) {
474                         nonSystem.add(perm)
475                     } else {
476                         system.add(perm)
477                     }
478                 }
479                 if (any && missing.size == values.size) {
480                     reporter.report(
481                         Issues.REQUIRES_PERMISSION,
482                         method,
483                         "None of the permissions ${missing.joinToString()} are defined by manifest " +
484                             "${config.manifest}."
485                     )
486                 }
487 
488                 if (system.isEmpty() && nonSystem.isEmpty()) {
489                     hasAnnotation = false
490                 } else if (any && nonSystem.isNotEmpty() || !any && system.isEmpty()) {
491                     reporter.report(
492                         Issues.REQUIRES_PERMISSION,
493                         method,
494                         "Method '" +
495                             method.name() +
496                             "' must be protected with a system permission; it currently" +
497                             " allows non-system callers holding " +
498                             nonSystem.toString()
499                     )
500                 }
501             }
502         }
503 
504         if (!hasAnnotation) {
505             reporter.report(
506                 Issues.REQUIRES_PERMISSION,
507                 method,
508                 "Method '" + method.name() + "' must be protected with a system permission."
509             )
510         }
511     }
512 
513     fun performChecks() {
514         if (codebase.trustedApi()) {
515             // The codebase is already an API; no consistency checks to be performed
516             return
517         }
518 
519         val checkSystemApi =
520             !reporter.isSuppressed(Issues.REQUIRES_PERMISSION) &&
521                 config.allShowAnnotations.matches(ANDROID_SYSTEM_API) &&
522                 !config.manifest.isEmpty()
523         val checkHiddenShowAnnotations =
524             !reporter.isSuppressed(Issues.UNHIDDEN_SYSTEM_API) &&
525                 config.allShowAnnotations.isNotEmpty()
526 
527         codebase.accept(
528             object :
529                 ApiVisitor(
530                     apiPredicateConfig = @Suppress("DEPRECATION") options.apiPredicateConfig,
531                 ) {
532                 override fun visitParameter(parameter: ParameterItem) {
533                     checkTypeReferencesHidden(parameter, parameter.type())
534                 }
535 
536                 /**
537                  * Visit all [SelectableItem]s, i.e. all [Item]s apart from [ParameterItem]s.
538                  *
539                  * None of the checks in this apply to [ParameterItem]. The deprecation checks do
540                  * not apply as there is no way to provide an `@deprecation` tag in Javadoc for
541                  * parameters. The unhidden showability annotation check ('UnhiddemSystemApi`) does
542                  * not apply as you cannot annotate a [ParameterItem] with a showability annotation.
543                  */
544                 override fun visitSelectableItem(item: SelectableItem) {
545                     if (
546                         item.originallyDeprecated &&
547                             !item.documentationContainsDeprecated() &&
548                             // Don't warn about this in Kotlin; the Kotlin deprecation annotation
549                             // includes deprecation
550                             // messages (unlike java.lang.Deprecated which has no attributes).
551                             // Instead, these
552                             // are added to the documentation by the [DocAnalyzer].
553                             !item.isKotlin() &&
554                             // @DeprecatedForSdk will show up as an alias for @Deprecated, but it's
555                             // correct
556                             // and expected to *not* combine this with @deprecated in the text;
557                             // here,
558                             // the text comes from an annotation attribute.
559                             item.modifiers.isAnnotatedWith(JAVA_LANG_DEPRECATED)
560                     ) {
561                         reporter.report(
562                             Issues.DEPRECATION_MISMATCH,
563                             item,
564                             "${item.toString().capitalize()}: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match"
565                         )
566                         // TODO: Check opposite (doc tag but no annotation)
567                     } else {
568                         val deprecatedForSdk =
569                             item.modifiers.findAnnotation(ANDROID_DEPRECATED_FOR_SDK)
570                         if (deprecatedForSdk != null) {
571                             if (item.documentation.hasTagSection("@deprecated")) {
572                                 reporter.report(
573                                     Issues.DEPRECATION_MISMATCH,
574                                     item,
575                                     "${item.toString().capitalize()}: Documentation contains `@deprecated` which implies this API is fully deprecated, not just @DeprecatedForSdk"
576                                 )
577                             } else {
578                                 val value = deprecatedForSdk.findAttribute(ANNOTATION_ATTR_VALUE)
579                                 val message = value?.legacyValue?.value()?.toString() ?: ""
580                                 item.appendDocumentation(message, "@deprecated")
581                             }
582                         }
583                     }
584 
585                     if (
586                         checkHiddenShowAnnotations &&
587                             item.hasShowAnnotation() &&
588                             !item.originallyHidden &&
589                             !item.showability.showNonRecursive()
590                     ) {
591                         item.modifiers
592                             .annotations()
593                             // Find the first show annotation. Just because item.hasShowAnnotation()
594                             // is true does not mean that there must be one show annotation as a
595                             // revert annotation could be treated as a show annotation on one item
596                             // and a hide annotation on another but is neither a show or hide
597                             // annotation.
598                             .firstOrNull(AnnotationItem::isShowAnnotation)
599                             // All show annotations must have a non-null string otherwise they
600                             // would not have been matched.
601                             ?.qualifiedName
602                             ?.removePrefix(ANDROID_ANNOTATION_PREFIX)
603                             ?.let { annotationName ->
604                                 reporter.report(
605                                     Issues.UNHIDDEN_SYSTEM_API,
606                                     item,
607                                     "@$annotationName APIs must also be marked @hide: ${item.describe()}"
608                                 )
609                             }
610                     }
611                 }
612 
613                 override fun visitClass(cls: ClassItem) {
614                     if (checkSystemApi) {
615                         // Look for Android @SystemApi exposed outside the normal SDK; we require
616                         // that they're protected with a system permission.
617                         // Also flag @SystemApi apis not annotated with @hide.
618 
619                         // This class is a system service if it's annotated with @SystemService,
620                         // or if it's android.content.pm.PackageManager
621                         if (
622                             cls.modifiers.isAnnotatedWith("android.annotation.SystemService") ||
623                                 cls.qualifiedName() == "android.content.pm.PackageManager"
624                         ) {
625                             // Check permissions on system services
626                             for (method in cls.filteredMethods(filterEmit)) {
627                                 checkSystemPermissions(method)
628                             }
629                         }
630                     }
631                 }
632 
633                 override fun visitField(field: FieldItem) {
634                     checkTypeReferencesHidden(field, field.type())
635                 }
636 
637                 override fun visitProperty(property: PropertyItem) {
638                     checkTypeReferencesHidden(property, property.type())
639                 }
640 
641                 override fun visitMethod(method: MethodItem) {
642                     checkTypeReferencesHidden(
643                         method,
644                         method.returnType()
645                     ) // returnType is nullable only for constructors
646                 }
647 
648                 /** Check that the type doesn't refer to any hidden classes. */
649                 private fun checkTypeReferencesHidden(item: Item, type: TypeItem) {
650                     type.accept(
651                         object : BaseTypeVisitor() {
652                             override fun visitClassType(classType: ClassTypeItem) {
653                                 val cls = classType.asClass() ?: return
654                                 if (
655                                     !filterReference.test(cls) &&
656                                         cls.origin != ClassOrigin.CLASS_PATH
657                                 ) {
658                                     reporter.report(
659                                         Issues.HIDDEN_TYPE_PARAMETER,
660                                         item,
661                                         "${item.toString().capitalize()} references hidden type $classType."
662                                     )
663                                 }
664                             }
665                         }
666                     )
667                 }
668             }
669         )
670     }
671 
672     // TODO: Switch to visitor iteration
673     fun handleStripping() {
674         val notStrippable = HashSet<ClassItem>(5000)
675 
676         val filter = ApiPredicate(config = config.apiPredicateConfig.copy(ignoreShown = true))
677 
678         // If a class is public or protected, not hidden, not imported and marked as included,
679         // then we can't strip it
680         val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList()
681         allTopLevelClasses
682             .filter { it.isApiCandidate() && it.emit && !it.hidden() }
683             .forEach { cantStripThis(it, filter, notStrippable, it, "self") }
684 
685         // complain about anything that looks includeable but is not supposed to
686         // be written, e.g. hidden things
687         for (cl in notStrippable) {
688             if (!cl.isHiddenOrRemoved()) {
689                 val publiclyConstructable =
690                     !cl.modifiers.isSealed() && cl.constructors().any { it.isApiCandidate() }
691                 for (m in cl.methods()) {
692                     if (!m.isApiCandidate()) {
693                         if (publiclyConstructable && m.modifiers.isAbstract()) {
694                             reporter.report(
695                                 Issues.HIDDEN_ABSTRACT_METHOD,
696                                 m,
697                                 "${m.name()} cannot be hidden and abstract when " +
698                                     "${cl.simpleName()} has a visible constructor, in case a " +
699                                     "third-party attempts to subclass it."
700                             )
701                         }
702                         continue
703                     }
704                     if (m.isHiddenOrRemoved()) {
705                         reporter.report(
706                             Issues.UNAVAILABLE_SYMBOL,
707                             m,
708                             "Reference to unavailable method " + m.name()
709                         )
710                     } else if (m.originallyDeprecated) {
711                         // don't bother reporting deprecated methods unless they are public and
712                         // explicitly marked as deprecated.
713                         reporter.report(
714                             Issues.DEPRECATED,
715                             m,
716                             "Method " + cl.qualifiedName() + "." + m.name() + " is deprecated"
717                         )
718                     }
719 
720                     checkTypeReferencesHiddenOrDeprecated(m.returnType(), m, cl, "Return type")
721                     for (p in m.parameters()) {
722                         checkTypeReferencesHiddenOrDeprecated(p.type(), m, cl, "Parameter")
723                     }
724                 }
725 
726                 if (!cl.effectivelyDeprecated) {
727                     val s = cl.superClass()
728                     if (s?.effectivelyDeprecated == true) {
729                         reporter.report(
730                             Issues.EXTENDS_DEPRECATED,
731                             cl,
732                             "Extending deprecated super class $s from ${cl.qualifiedName()}: this class should also be deprecated"
733                         )
734                     }
735 
736                     for (t in cl.interfaceTypes()) {
737                         if (t.asClass()?.effectivelyDeprecated == true) {
738                             reporter.report(
739                                 Issues.EXTENDS_DEPRECATED,
740                                 cl,
741                                 "Implementing interface of deprecated type $t in ${cl.qualifiedName()}: this class should also be deprecated"
742                             )
743                         }
744                     }
745                 }
746             } else if (cl.originallyDeprecated) {
747                 // not hidden, but deprecated
748                 reporter.report(Issues.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated")
749             }
750         }
751     }
752 
753     private fun cantStripThis(
754         cl: ClassItem,
755         filter: FilterPredicate,
756         notStrippable: MutableSet<ClassItem>,
757         from: Item,
758         usage: String
759     ) {
760         if (cl.origin == ClassOrigin.CLASS_PATH) {
761             return
762         }
763 
764         if (cl.isHiddenOrRemoved() || cl.isPackagePrivate && !cl.isApiCandidate()) {
765             reporter.report(
766                 Issues.REFERENCES_HIDDEN,
767                 from,
768                 "Class ${cl.qualifiedName()} is ${if (cl.isHiddenOrRemoved()) "hidden" else "not public"} but was referenced ($usage) from public ${from.describe(
769                     false
770                 )}"
771             )
772         }
773 
774         if (!notStrippable.add(cl)) {
775             // slight optimization: if it already contains cl, it already contains
776             // all of cl's parents
777             return
778         }
779 
780         // can't strip any public fields or their generics
781         for (field in cl.fields()) {
782             if (!filter.test(field)) {
783                 continue
784             }
785             cantStripThis(field.type(), field, filter, notStrippable, "in field type")
786         }
787         // can't strip any of the type's generics
788         cantStripThis(cl.typeParameterList, filter, notStrippable, cl)
789         // can't strip any of the annotation elements
790         // cantStripThis(cl.annotationElements(), notStrippable);
791         // take care of methods
792         cantStripThis(cl.methods(), filter, notStrippable)
793         cantStripThis(cl.constructors(), filter, notStrippable)
794         // blow the outer class open if this is an inner class
795         val containingClass = cl.containingClass()
796         if (containingClass != null) {
797             cantStripThis(containingClass, filter, notStrippable, cl, "as containing class")
798         }
799         // all visible inner classes will be included in stubs
800         cl.nestedClasses()
801             .filter { it.isApiCandidate() }
802             .forEach { cantStripThis(it, filter, notStrippable, cl, "as nested class") }
803         // blow open super class and interfaces
804         // TODO: Consider using val superClass = cl.filteredSuperclass(filter)
805         val superItems = cl.allInterfaces().toMutableSet()
806         cl.superClass()?.let { superClass -> superItems.add(superClass) }
807 
808         for (superItem in superItems) {
809             // allInterfaces includes cl itself if cl is an interface
810             if (superItem.isHiddenOrRemoved() && superItem != cl) {
811                 // cl is a public class declared as extending a hidden superclass.
812                 // this is not a desired practice, but it's happened, so we deal
813                 // with it by finding the first super class which passes checkLevel for purposes of
814                 // generating the doc & stub information, and proceeding normally.
815                 if (superItem.origin != ClassOrigin.CLASS_PATH) {
816                     reporter.report(
817                         Issues.HIDDEN_SUPERCLASS,
818                         cl,
819                         "Public class " +
820                             cl.qualifiedName() +
821                             " stripped of unavailable superclass " +
822                             superItem.qualifiedName()
823                     )
824                 }
825             } else {
826                 // doclava would also mark the package private super classes as unhidden, but that's
827                 // not
828                 // right (this was just done for its stub handling)
829                 //   cantStripThis(superClass, filter, notStrippable, stubImportPackages, cl, "as
830                 // super class")
831 
832                 if (superItem.isPrivate && superItem.origin != ClassOrigin.CLASS_PATH) {
833                     reporter.report(
834                         Issues.PRIVATE_SUPERCLASS,
835                         cl,
836                         "Public class " +
837                             cl.qualifiedName() +
838                             " extends private class " +
839                             superItem.qualifiedName()
840                     )
841                 }
842             }
843         }
844     }
845 
846     private fun cantStripThis(
847         callables: List<CallableItem>,
848         filter: FilterPredicate,
849         notStrippable: MutableSet<ClassItem>,
850     ) {
851         // for each callable, blow open the parameters, throws and return types. also blow open
852         // their generics
853         for (callable in callables) {
854             if (!filter.test(callable)) {
855                 continue
856             }
857             cantStripThis(callable.typeParameterList, filter, notStrippable, callable)
858             for (parameter in callable.parameters()) {
859                 cantStripThis(
860                     parameter.type(),
861                     parameter,
862                     filter,
863                     notStrippable,
864                     "in parameter type"
865                 )
866             }
867             for (thrown in callable.throwsTypes()) {
868                 if (thrown is VariableTypeItem) continue
869                 val classItem = thrown.erasedClass ?: continue
870                 cantStripThis(classItem, filter, notStrippable, callable, "as exception")
871             }
872             cantStripThis(callable.returnType(), callable, filter, notStrippable, "in return type")
873         }
874     }
875 
876     private fun cantStripThis(
877         typeParameterList: TypeParameterList,
878         filter: FilterPredicate,
879         notStrippable: MutableSet<ClassItem>,
880         context: Item
881     ) {
882         for (typeParameter in typeParameterList) {
883             for (bound in typeParameter.typeBounds()) {
884                 cantStripThis(bound, context, filter, notStrippable, "as type parameter")
885             }
886         }
887     }
888 
889     private fun cantStripThis(
890         type: TypeItem,
891         context: Item,
892         filter: FilterPredicate,
893         notStrippable: MutableSet<ClassItem>,
894         usage: String,
895     ) {
896         type.accept(
897             object : BaseTypeVisitor() {
898                 override fun visitClassType(classType: ClassTypeItem) {
899                     val asClass = classType.asClass() ?: return
900                     cantStripThis(asClass, filter, notStrippable, context, usage)
901                 }
902             }
903         )
904     }
905 
906     /**
907      * Checks if the type (method parameter or return type) references a hidden or deprecated class.
908      */
909     private fun checkTypeReferencesHiddenOrDeprecated(
910         type: TypeItem,
911         containingMethod: MethodItem,
912         containingClass: ClassItem,
913         usage: String
914     ) {
915         if (!containingMethod.effectivelyDeprecated) {
916             type.accept(
917                 object : BaseTypeVisitor() {
918                     override fun visitClassType(classType: ClassTypeItem) {
919                         if (classType.asClass()?.effectivelyDeprecated == true) {
920                             reporter.report(
921                                 Issues.REFERENCES_DEPRECATED,
922                                 containingMethod,
923                                 "$usage references deprecated type $classType in ${containingClass.qualifiedName()}.${containingMethod.name()}(): this method should also be deprecated"
924                             )
925                         }
926                     }
927                 }
928             )
929         }
930 
931         val hiddenClasses = findHiddenClasses(type)
932         val typeClassName = (type as? ClassTypeItem)?.qualifiedName
933         for (hiddenClass in hiddenClasses) {
934             if (hiddenClass.origin == ClassOrigin.CLASS_PATH) continue
935             if (hiddenClass.qualifiedName() == typeClassName) {
936                 // The type itself is hidden
937                 reporter.report(
938                     Issues.UNAVAILABLE_SYMBOL,
939                     containingMethod,
940                     "$usage of unavailable type $type in ${containingClass.qualifiedName()}.${containingMethod.name()}()"
941                 )
942             } else {
943                 // The type contains a hidden type
944                 reporter.report(
945                     Issues.HIDDEN_TYPE_PARAMETER,
946                     containingMethod,
947                     "$usage uses type parameter of unavailable type $type in ${containingClass.qualifiedName()}.${containingMethod.name()}()"
948                 )
949             }
950         }
951     }
952 
953     /**
954      * Find references to hidden classes.
955      *
956      * This finds hidden classes that are used by public parts of the API in order to ensure the API
957      * is self-consistent and does not reference classes that are not included in the stubs. Any
958      * such references cause an error to be reported.
959      *
960      * A reference to an imported class is not treated as an error, even though imported classes are
961      * hidden from the stub generation. That is because imported classes are, by definition,
962      * excluded from the set of classes for which stubs are required.
963      *
964      * @param ti the type information to examine for references to hidden classes.
965      * @return all references to hidden classes referenced by the type
966      */
967     private fun findHiddenClasses(ti: TypeItem): Set<ClassItem> {
968         val hiddenClasses = mutableSetOf<ClassItem>()
969         ti.accept(
970             object : BaseTypeVisitor() {
971                 override fun visitClassType(classType: ClassTypeItem) {
972                     val asClass = classType.asClass() ?: return
973                     if (asClass.isHiddenOrRemoved()) {
974                         hiddenClasses.add(asClass)
975                     }
976                 }
977             }
978         )
979         return hiddenClasses
980     }
981 }
982 
Stringnull983 private fun String.capitalize(): String {
984     return this.replaceFirstChar {
985         if (it.isLowerCase()) {
986             it.titlecase(Locale.getDefault())
987         } else {
988             it.toString()
989         }
990     }
991 }
992 
993 /** Returns true if this item is public or protected and so a candidate for inclusion in an API. */
SelectableItemnull994 private fun SelectableItem.isApiCandidate(): Boolean {
995     return !isHiddenOrRemoved() && (modifiers.isPublic() || modifiers.isProtected())
996 }
997 
998 /**
999  * Whether documentation for the [Item] has the `@deprecated` tag -- for inherited methods, this
1000  * also looks at any inherited documentation.
1001  */
Itemnull1002 private fun Item.documentationContainsDeprecated(): Boolean {
1003     val text = documentation.text
1004     if (text.contains("@deprecated")) return true
1005     if (this is MethodItem && (text == "" || text.contains("@inheritDoc"))) {
1006         return superMethods().any { it.documentationContainsDeprecated() }
1007     }
1008     return false
1009 }
1010 
1011 /**
1012  * A set of [MethodItem]s.
1013  *
1014  * This is implemented as a [MutableMap] from the [MethodItem.name] to the list of [MethodItem]s
1015  * with that name.
1016  */
1017 private typealias MethodItemSet = HashMap<String, MutableList<MethodItem>>
1018 
1019 /**
1020  * Add a method to the set.
1021  *
1022  * This does not check to see if the [MethodItem] exists already so it is possible that it will
1023  * contain duplicate methods.
1024  */
addnull1025 private fun MethodItemSet.add(method: MethodItem) {
1026     val name = method.name()
1027     val list = computeIfAbsent(name) { mutableListOf() }
1028     list.add(method)
1029 }
1030 
1031 /**
1032  * Check to see whether the set contains a method that matches [method] as determined by
1033  * [MethodItem.matches].
1034  */
containsMatchingMethodnull1035 private fun MethodItemSet.containsMatchingMethod(method: MethodItem): Boolean {
1036     val name = method.name()
1037     val list = this[name] ?: return false
1038     for (existing in list) {
1039         if (method.matches(existing)) {
1040             return true
1041         }
1042     }
1043     return false
1044 }
1045 
1046 /** Remove any method that matches [method] as determined by [MethodItem.matches]. */
MethodItemSetnull1047 private fun MethodItemSet.removeMatchingMethods(method: MethodItem) {
1048     val name = method.name()
1049     val list = this[name] ?: return
1050     val iterator = list.listIterator()
1051     while (iterator.hasNext()) {
1052         val existing = iterator.next()
1053         if (method.matches(existing)) {
1054             iterator.remove()
1055         }
1056     }
1057 }
1058