1 /*
2  * 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.NullnessMigration.Companion.findNullnessAnnotation
20 import com.android.tools.metalava.NullnessMigration.Companion.isNullable
21 import com.android.tools.metalava.doclava1.ApiPredicate
22 import com.android.tools.metalava.doclava1.Issues
23 import com.android.tools.metalava.doclava1.Issues.Issue
24 import com.android.tools.metalava.model.text.TextCodebase
25 import com.android.tools.metalava.model.AnnotationItem
26 import com.android.tools.metalava.model.ClassItem
27 import com.android.tools.metalava.model.Codebase
28 import com.android.tools.metalava.model.FieldItem
29 import com.android.tools.metalava.model.Item
30 import com.android.tools.metalava.model.Item.Companion.describe
31 import com.android.tools.metalava.model.MethodItem
32 import com.android.tools.metalava.model.PackageItem
33 import com.android.tools.metalava.model.ParameterItem
34 import com.android.tools.metalava.model.TypeItem
35 import com.android.tools.metalava.model.configuration
36 import com.intellij.psi.PsiField
37 import java.io.File
38 import java.util.function.Predicate
39 
40 /**
41  * Compares the current API with a previous version and makes sure
42  * the changes are compatible. For example, you can make a previously
43  * nullable parameter non null, but not vice versa.
44  *
45  * TODO: Only allow nullness changes on final classes!
46  */
47 class CompatibilityCheck(
48     val filterReference: Predicate<Item>,
49     private val oldCodebase: Codebase,
50     private val apiType: ApiType,
51     private val base: Codebase? = null,
52     private val reporter: Reporter
53 ) : ComparisonVisitor() {
54 
55     /**
56      * Request for compatibility checks.
57      * [file] represents the signature file to be checked. [apiType] represents which
58      * part of the API should be checked, [releaseType] represents what kind of codebase
59      * we are comparing it against. If [codebase] is specified, compare the signature file
60      * against the codebase instead of metalava's current source tree configured via the
61      * normal source path flags.
62      */
63     data class CheckRequest(
64         val file: File,
65         val apiType: ApiType,
66         val releaseType: ReleaseType,
67         val codebase: File? = null
68     ) {
toStringnull69         override fun toString(): String {
70             return "--check-compatibility:${apiType.flagName}:${releaseType.flagName} $file"
71         }
72     }
73 
74     /** In old signature files, methods inherited from hidden super classes
75      * are not included. An example of this is StringBuilder.setLength.
76      * More details about this are listed in Compatibility.skipInheritedMethods.
77      * We may see these in the codebase but not in the (old) signature files,
78      * so in these cases we want to ignore certain changes such as considering
79      * StringBuilder.setLength a newly added method.
80      */
81     private val comparingWithPartialSignatures = oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1
82 
83     var foundProblems = false
84 
comparenull85     override fun compare(old: Item, new: Item) {
86         val oldModifiers = old.modifiers
87         val newModifiers = new.modifiers
88         if (oldModifiers.isOperator() && !newModifiers.isOperator()) {
89             report(
90                 Issues.OPERATOR_REMOVAL,
91                 new,
92                 "Cannot remove `operator` modifier from ${describe(new)}: Incompatible change"
93             )
94         }
95 
96         if (oldModifiers.isInfix() && !newModifiers.isInfix()) {
97             report(
98                 Issues.INFIX_REMOVAL,
99                 new,
100                 "Cannot remove `infix` modifier from ${describe(new)}: Incompatible change"
101             )
102         }
103 
104         // Should not remove nullness information
105         // Can't change information incompatibly
106         val oldNullnessAnnotation = findNullnessAnnotation(old)
107         if (oldNullnessAnnotation != null) {
108             val newNullnessAnnotation = findNullnessAnnotation(new)
109             if (newNullnessAnnotation == null) {
110                 val implicitNullness = AnnotationItem.getImplicitNullness(new)
111                 if (implicitNullness == true && isNullable(old)) {
112                     return
113                 }
114                 if (implicitNullness == false && !isNullable(old)) {
115                     return
116                 }
117                 val name = AnnotationItem.simpleName(oldNullnessAnnotation)
118                 if (old.type()?.primitive == true) {
119                     return
120                 }
121                 report(
122                     Issues.INVALID_NULL_CONVERSION, new,
123                     "Attempted to remove $name annotation from ${describe(new)}"
124                 )
125             } else {
126                 val oldNullable = isNullable(old)
127                 val newNullable = isNullable(new)
128                 if (oldNullable != newNullable) {
129                     // You can change a parameter from nonnull to nullable
130                     // You can change a method from nullable to nonnull
131                     // You cannot change a parameter from nullable to nonnull
132                     // You cannot change a method from nonnull to nullable
133                     if (oldNullable && old is ParameterItem) {
134                         report(
135                             Issues.INVALID_NULL_CONVERSION,
136                             new,
137                             "Attempted to change parameter from @Nullable to @NonNull: " +
138                                 "incompatible change for ${describe(new)}"
139                         )
140                     } else if (!oldNullable && old is MethodItem) {
141                         report(
142                             Issues.INVALID_NULL_CONVERSION,
143                             new,
144                             "Attempted to change method return from @NonNull to @Nullable: " +
145                                 "incompatible change for ${describe(new)}"
146                         )
147                     }
148                 }
149             }
150         }
151     }
152 
comparenull153     override fun compare(old: ParameterItem, new: ParameterItem) {
154         val prevName = old.publicName() ?: return
155         val newName = new.publicName()
156         if (newName == null) {
157             report(
158                 Issues.PARAMETER_NAME_CHANGE,
159                 new,
160                 "Attempted to remove parameter name from ${describe(new)} in ${describe(new.containingMethod())}"
161             )
162         } else if (newName != prevName) {
163             report(
164                 Issues.PARAMETER_NAME_CHANGE,
165                 new,
166                 "Attempted to change parameter name from $prevName to $newName in ${describe(new.containingMethod())}"
167             )
168         }
169 
170         if (old.hasDefaultValue() && !new.hasDefaultValue()) {
171             report(
172                 Issues.DEFAULT_VALUE_CHANGE,
173                 new,
174                 "Attempted to remove default value from ${describe(new)} in ${describe(new.containingMethod())}"
175             )
176         }
177 
178         if (old.isVarArgs() && !new.isVarArgs()) {
179             // In Java, changing from array to varargs is a compatible change, but
180             // not the other way around. Kotlin is the same, though in Kotlin
181             // you have to change the parameter type as well to an array type; assuming you
182             // do that it's the same situation as Java; otherwise the normal
183             // signature check will catch the incompatibility.
184             report(
185                 Issues.VARARG_REMOVAL,
186                 new,
187                 "Changing from varargs to array is an incompatible change: ${describe(
188                     new,
189                     includeParameterTypes = true,
190                     includeParameterNames = true
191                 )}"
192             )
193         }
194     }
195 
comparenull196     override fun compare(old: ClassItem, new: ClassItem) {
197         val oldModifiers = old.modifiers
198         val newModifiers = new.modifiers
199 
200         if (old.isInterface() != new.isInterface()) {
201             report(
202                 Issues.CHANGED_CLASS, new, "${describe(new, capitalize = true)} changed class/interface declaration"
203             )
204             return // Avoid further warnings like "has changed abstract qualifier" which is implicit in this change
205         }
206 
207         for (iface in old.interfaceTypes()) {
208             val qualifiedName = iface.asClass()?.qualifiedName() ?: continue
209             if (!new.implements(qualifiedName)) {
210                 report(
211                     Issues.REMOVED_INTERFACE, new, "${describe(old, capitalize = true)} no longer implements $iface"
212                 )
213             }
214         }
215 
216         for (iface in new.filteredInterfaceTypes(filterReference)) {
217             val qualifiedName = iface.asClass()?.qualifiedName() ?: continue
218             if (!old.implements(qualifiedName)) {
219                 report(
220                     Issues.ADDED_INTERFACE, new, "Added interface $iface to class ${describe(old)}"
221                 )
222             }
223         }
224 
225         if (!oldModifiers.isSealed() && newModifiers.isSealed()) {
226             report(Issues.ADD_SEALED, new, "Cannot add 'sealed' modifier to ${describe(new)}: Incompatible change")
227         } else if (old.isClass() && oldModifiers.isAbstract() != newModifiers.isAbstract()) {
228             report(
229                 Issues.CHANGED_ABSTRACT, new, "${describe(new, capitalize = true)} changed 'abstract' qualifier"
230             )
231         }
232 
233         // Check for changes in final & static, but not in enums (since PSI and signature files differ
234         // a bit in whether they include these for enums
235         if (!new.isEnum()) {
236             if (!oldModifiers.isFinal() && newModifiers.isFinal()) {
237                 // It is safe to make a class final if it did not previously have any public
238                 // constructors because it was impossible for an application to create a subclass.
239                 if (old.constructors().filter { it.isPublic || it.isProtected }.none()) {
240                     report(
241                         Issues.ADDED_FINAL_UNINSTANTIABLE, new,
242                         "${describe(
243                             new,
244                             capitalize = true
245                         )} added 'final' qualifier but was previously uninstantiable and therefore could not be subclassed"
246                     )
247                 } else {
248                     report(
249                         Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} added 'final' qualifier"
250                     )
251                 }
252             } else if (oldModifiers.isFinal() && !newModifiers.isFinal()) {
253                 report(
254                     Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} removed 'final' qualifier"
255                 )
256             }
257 
258             if (oldModifiers.isStatic() != newModifiers.isStatic()) {
259                 val hasPublicConstructor = old.constructors().any { it.isPublic }
260                 if (!old.isInnerClass() || hasPublicConstructor) {
261                     report(
262                         Issues.CHANGED_STATIC,
263                         new,
264                         "${describe(new, capitalize = true)} changed 'static' qualifier"
265                     )
266                 }
267             }
268         }
269 
270         val oldVisibility = oldModifiers.getVisibilityString()
271         val newVisibility = newModifiers.getVisibilityString()
272         if (oldVisibility != newVisibility) {
273             // TODO: Use newModifiers.asAccessibleAs(oldModifiers) to provide different error messages
274             // based on whether this seems like a reasonable change, e.g. making a private or final method more
275             // accessible is fine (no overridden method affected) but not making methods less accessible etc
276             report(
277                 Issues.CHANGED_SCOPE, new,
278                 "${describe(new, capitalize = true)} changed visibility from $oldVisibility to $newVisibility"
279             )
280         }
281 
282         if (!old.deprecated == new.deprecated) {
283             report(
284                 Issues.CHANGED_DEPRECATED, new,
285                 "${describe(
286                     new,
287                     capitalize = true
288                 )} has changed deprecation state ${old.deprecated} --> ${new.deprecated}"
289             )
290         }
291 
292         val oldSuperClassName = old.superClass()?.qualifiedName()
293         if (oldSuperClassName != null) { // java.lang.Object can't have a superclass.
294             if (!new.extends(oldSuperClassName)) {
295                 report(
296                     Issues.CHANGED_SUPERCLASS, new,
297                     "${describe(
298                         new,
299                         capitalize = true
300                     )} superclass changed from $oldSuperClassName to ${new.superClass()?.qualifiedName()}"
301                 )
302             }
303         }
304 
305         if (old.hasTypeVariables() && new.hasTypeVariables()) {
306             val oldTypeParamsCount = old.typeParameterList().typeParameterCount()
307             val newTypeParamsCount = new.typeParameterList().typeParameterCount()
308             if (oldTypeParamsCount != newTypeParamsCount) {
309                 report(
310                     Issues.CHANGED_TYPE, new,
311                     "${describe(
312                         old,
313                         capitalize = true
314                     )} changed number of type parameters from $oldTypeParamsCount to $newTypeParamsCount"
315                 )
316             }
317         }
318     }
319 
comparenull320     override fun compare(old: MethodItem, new: MethodItem) {
321         val oldModifiers = old.modifiers
322         val newModifiers = new.modifiers
323 
324         val oldReturnType = old.returnType()
325         val newReturnType = new.returnType()
326         if (!new.isConstructor() && oldReturnType != null && newReturnType != null) {
327             val oldTypeParameter = oldReturnType.asTypeParameter(old)
328             val newTypeParameter = newReturnType.asTypeParameter(new)
329             var compatible = true
330             if (oldTypeParameter == null &&
331                 newTypeParameter == null
332             ) {
333                 if (oldReturnType != newReturnType ||
334                     oldReturnType.arrayDimensions() != newReturnType.arrayDimensions()
335                 ) {
336                     compatible = false
337                 }
338             } else if (oldTypeParameter == null && newTypeParameter != null) {
339                 val constraints = newTypeParameter.bounds()
340                 for (constraint in constraints) {
341                     val oldClass = oldReturnType.asClass()
342                     if (oldClass == null || !oldClass.extendsOrImplements(constraint.qualifiedName())) {
343                         compatible = false
344                     }
345                 }
346             } else if (oldTypeParameter != null && newTypeParameter == null) {
347                 // It's never valid to go from being a parameterized type to not being one.
348                 // This would drop the implicit cast breaking backwards compatibility.
349                 compatible = false
350             } else {
351                 // If both return types are parameterized then the constraints must be
352                 // exactly the same.
353                 val oldConstraints = oldTypeParameter?.bounds() ?: emptyList()
354                 val newConstraints = newTypeParameter?.bounds() ?: emptyList()
355                 if (oldConstraints.size != newConstraints.size ||
356                     newConstraints != oldConstraints
357                 ) {
358                     val oldTypeString = describeBounds(oldReturnType, oldConstraints)
359                     val newTypeString = describeBounds(newReturnType, newConstraints)
360                     val message =
361                         "${describe(
362                             new,
363                             capitalize = true
364                         )} has changed return type from $oldTypeString to $newTypeString"
365 
366                     report(Issues.CHANGED_TYPE, new, message)
367                     return
368                 }
369             }
370 
371             if (!compatible) {
372                 var oldTypeString = oldReturnType.toSimpleType()
373                 var newTypeString = newReturnType.toSimpleType()
374                 // Typically, show short type names like "String" if they're distinct (instead of long type names like
375                 // "java.util.Set<T!>")
376                 if (oldTypeString == newTypeString) {
377                     // If the short names aren't unique, then show full type names like "java.util.Set<T!>"
378                     oldTypeString = oldReturnType.toString()
379                     newTypeString = newReturnType.toString()
380                 }
381                 val message =
382                     "${describe(new, capitalize = true)} has changed return type from $oldTypeString to $newTypeString"
383                 report(Issues.CHANGED_TYPE, new, message)
384             }
385 
386             // Annotation methods?
387             if (!old.hasSameValue(new)) {
388                 val prevValue = old.defaultValue()
389                 val prevString = if (prevValue.isEmpty()) {
390                     "nothing"
391                 } else {
392                     prevValue
393                 }
394 
395                 val newValue = new.defaultValue()
396                 val newString = if (newValue.isEmpty()) {
397                     "nothing"
398                 } else {
399                     newValue
400                 }
401                 val message = "${describe(
402                     new,
403                     capitalize = true
404                 )} has changed value from $prevString to $newString"
405                 report(Issues.CHANGED_VALUE, new, message)
406             }
407         }
408 
409         // Check for changes in abstract, but only for regular classes; older signature files
410         // sometimes describe interface methods as abstract
411         if (new.containingClass().isClass()) {
412             if (!oldModifiers.isAbstract() && newModifiers.isAbstract() &&
413                 // In old signature files, overridden methods of abstract methods declared
414                 // in super classes are sometimes omitted by doclava. This means that the method
415                 // looks (from the signature file perspective) like it has not been implemented,
416                 // whereas in reality it has. For just one example of this, consider
417                 // FragmentBreadCrumbs.onLayout: it's a concrete implementation in that class
418                 // of the inherited method from ViewGroup. However, in the signature file,
419                 // FragmentBreadCrumbs does not list this method; it's only listed (as abstract)
420                 // in the super class. In this scenario, the compatibility check would believe
421                 // the old method in FragmentBreadCrumbs is abstract and the new method is not,
422                 // which is not the case. Therefore, if the old method is coming from a signature
423                 // file based codebase with an old format, we omit abstract change warnings.
424                 // The reverse situation can also happen: AbstractSequentialList defines listIterator
425                 // as abstract, but it's not recorded as abstract in the signature files anywhere,
426                 // so we treat this as a nearly abstract method, which it is not.
427                 (old.inheritedFrom == null || !comparingWithPartialSignatures)
428             ) {
429                 report(
430                     Issues.CHANGED_ABSTRACT, new, "${describe(new, capitalize = true)} has changed 'abstract' qualifier"
431                 )
432             }
433         }
434 
435         if (oldModifiers.isNative() != newModifiers.isNative()) {
436             report(
437                 Issues.CHANGED_NATIVE, new, "${describe(new, capitalize = true)} has changed 'native' qualifier"
438             )
439         }
440 
441         // Check changes to final modifier. But skip enums where it varies between signature files and PSI
442         // whether the methods are considered final.
443         if (!new.containingClass().isEnum() && !oldModifiers.isStatic()) {
444             // Skip changes in final; modifier change could come from inherited
445             // implementation from hidden super class. An example of this
446             // is SpannableString.charAt whose implementation comes from
447             // SpannableStringInternal.
448             if (old.inheritedFrom == null || !comparingWithPartialSignatures) {
449                 // Compiler-generated methods vary in their 'final' qualifier between versions of
450                 // the compiler, so this check needs to be quite narrow. A change in 'final'
451                 // status of a method is only relevant if (a) the method is not declared 'static'
452                 // and (b) the method is not already inferred to be 'final' by virtue of its class.
453                 if (!old.isEffectivelyFinal() && new.isEffectivelyFinal()) {
454                     report(
455                         Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
456                     )
457                 } else if (old.isEffectivelyFinal() && !new.isEffectivelyFinal()) {
458                     report(
459                         Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} has removed 'final' qualifier"
460                     )
461                 }
462             }
463         }
464 
465         if (oldModifiers.isStatic() != newModifiers.isStatic()) {
466             report(
467                 Issues.CHANGED_STATIC, new, "${describe(new, capitalize = true)} has changed 'static' qualifier"
468             )
469         }
470 
471         val oldVisibility = oldModifiers.getVisibilityString()
472         val newVisibility = newModifiers.getVisibilityString()
473         if (oldVisibility != newVisibility) {
474             // TODO: Use newModifiers.asAccessibleAs(oldModifiers) to provide different error messages
475             // based on whether this seems like a reasonable change, e.g. making a private or final method more
476             // accessible is fine (no overridden method affected) but not making methods less accessible etc
477             report(
478                 Issues.CHANGED_SCOPE, new,
479                 "${describe(new, capitalize = true)} changed visibility from $oldVisibility to $newVisibility"
480             )
481         }
482 
483         if (old.deprecated != new.deprecated) {
484             report(
485                 Issues.CHANGED_DEPRECATED, new,
486                 "${describe(
487                     new,
488                     capitalize = true
489                 )} has changed deprecation state ${old.deprecated} --> ${new.deprecated}"
490             )
491         }
492 
493         /*
494         // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
495         // "compatibility with existing binaries."
496         if (oldModifiers.isSynchronized() != newModifiers.isSynchronized()) {
497             report(
498                 Errors.CHANGED_SYNCHRONIZED, new,
499                 "${describe(
500                     new,
501                     capitalize = true
502                 )} has changed 'synchronized' qualifier from ${oldModifiers.isSynchronized()} to ${newModifiers.isSynchronized()}"
503             )
504         }
505         */
506 
507         for (exception in old.throwsTypes()) {
508             if (!new.throws(exception.qualifiedName())) {
509                 // exclude 'throws' changes to finalize() overrides with no arguments
510                 if (old.name() != "finalize" || old.parameters().isNotEmpty()) {
511                     report(
512                         Issues.CHANGED_THROWS, new,
513                         "${describe(new, capitalize = true)} no longer throws exception ${exception.qualifiedName()}"
514                     )
515                 }
516             }
517         }
518 
519         for (exec in new.filteredThrowsTypes(filterReference)) {
520             if (!old.throws(exec.qualifiedName())) {
521                 // exclude 'throws' changes to finalize() overrides with no arguments
522                 if (!(old.name() == "finalize" && old.parameters().isEmpty()) &&
523                     // exclude cases where throws clause was missing in signatures from
524                     // old enum methods
525                     !old.isEnumSyntheticMethod()) {
526                     val message = "${describe(new, capitalize = true)} added thrown exception ${exec.qualifiedName()}"
527                     report(Issues.CHANGED_THROWS, new, message)
528                 }
529             }
530         }
531 
532         if (new.modifiers.isInline()) {
533             val oldTypes = old.typeParameterList().typeParameters()
534             val newTypes = new.typeParameterList().typeParameters()
535             for (i in oldTypes.indices) {
536                 if (i == newTypes.size) {
537                     break
538                 }
539                 if (newTypes[i].isReified() && !oldTypes[i].isReified()) {
540                     val message = "${describe(
541                         new,
542                         capitalize = true
543                     )} made type variable ${newTypes[i].simpleName()} reified: incompatible change"
544                     report(Issues.CHANGED_THROWS, new, message)
545                 }
546             }
547         }
548     }
549 
describeBoundsnull550     private fun describeBounds(
551         type: TypeItem,
552         constraints: List<ClassItem>
553     ): String {
554         return type.toSimpleType() +
555             if (constraints.isEmpty()) {
556                 " (extends java.lang.Object)"
557             } else {
558                 " (extends ${constraints.joinToString(separator = " & ") { it.qualifiedName() }})"
559             }
560     }
561 
comparenull562     override fun compare(old: FieldItem, new: FieldItem) {
563         val oldModifiers = old.modifiers
564         val newModifiers = new.modifiers
565 
566         if (!old.isEnumConstant()) {
567             val oldType = old.type()
568             val newType = new.type()
569             if (oldType != newType) {
570                 val message = "${describe(new, capitalize = true)} has changed type from $oldType to $newType"
571                 report(Issues.CHANGED_TYPE, new, message)
572             } else if (!old.hasSameValue(new)) {
573                 val prevValue = old.initialValue(true)
574                 val prevString = if (prevValue == null && !old.modifiers.isFinal()) {
575                     "nothing/not constant"
576                 } else {
577                     prevValue
578                 }
579 
580                 val newValue = new.initialValue(true)
581                 val newString = if (newValue is PsiField) {
582                     newValue.containingClass?.qualifiedName + "." + newValue.name
583                 } else {
584                     newValue
585                 }
586                 val message = "${describe(
587                     new,
588                     capitalize = true
589                 )} has changed value from $prevString to $newString"
590 
591                 if (message == "Field android.telephony.data.ApnSetting.TYPE_DEFAULT has changed value from 17 to 1") {
592                     // Temporarily ignore: this value changed incompatibly from 28.txt to current.txt.
593                     // It's not clear yet whether this value change needs to be reverted, or suppressed
594                     // permanently in the source code, but suppressing from metalava so we can unblock
595                     // getting the compatibility checks enabled.
596                 } else
597                     report(Issues.CHANGED_VALUE, new, message)
598             }
599         }
600 
601         val oldVisibility = oldModifiers.getVisibilityString()
602         val newVisibility = newModifiers.getVisibilityString()
603         if (oldVisibility != newVisibility) {
604             // TODO: Use newModifiers.asAccessibleAs(oldModifiers) to provide different error messages
605             // based on whether this seems like a reasonable change, e.g. making a private or final method more
606             // accessible is fine (no overridden method affected) but not making methods less accessible etc
607             report(
608                 Issues.CHANGED_SCOPE, new,
609                 "${describe(new, capitalize = true)} changed visibility from $oldVisibility to $newVisibility"
610             )
611         }
612 
613         if (oldModifiers.isStatic() != newModifiers.isStatic()) {
614             report(
615                 Issues.CHANGED_STATIC, new, "${describe(new, capitalize = true)} has changed 'static' qualifier"
616             )
617         }
618 
619         if (!oldModifiers.isFinal() && newModifiers.isFinal()) {
620             report(
621                 Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
622             )
623         } else if (oldModifiers.isFinal() && !newModifiers.isFinal()) {
624             report(
625                 Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} has removed 'final' qualifier"
626             )
627         }
628 
629         if (oldModifiers.isTransient() != newModifiers.isTransient()) {
630             report(
631                 Issues.CHANGED_TRANSIENT, new, "${describe(new, capitalize = true)} has changed 'transient' qualifier"
632             )
633         }
634 
635         if (oldModifiers.isVolatile() != newModifiers.isVolatile()) {
636             report(
637                 Issues.CHANGED_VOLATILE, new, "${describe(new, capitalize = true)} has changed 'volatile' qualifier"
638             )
639         }
640 
641         if (old.deprecated != new.deprecated) {
642             report(
643                 Issues.CHANGED_DEPRECATED, new,
644                 "${describe(
645                     new,
646                     capitalize = true
647                 )} has changed deprecation state ${old.deprecated} --> ${new.deprecated}"
648             )
649         }
650     }
651 
handleAddednull652     private fun handleAdded(issue: Issue, item: Item) {
653         if (item.originallyHidden) {
654             // This is an element which is hidden but is referenced from
655             // some public API. This is an error, but some existing code
656             // is doing this. This is not an API addition.
657             return
658         }
659 
660         var message = "Added ${describe(item)}"
661 
662         // Clarify error message for removed API to make it less ambiguous
663         if (apiType == ApiType.REMOVED) {
664             message += " to the removed API"
665         } else if (options.showAnnotations.isNotEmpty()) {
666             if (options.showAnnotations.matchesSuffix("SystemApi")) {
667                 message += " to the system API"
668             } else if (options.showAnnotations.matchesSuffix("TestApi")) {
669                 message += " to the test API"
670             }
671         }
672 
673         // In some cases we run the comparison on signature files
674         // generated into the temp directory, but in these cases
675         // try to report the item against the real item in the API instead
676         val equivalent = findBaseItem(item)
677         if (equivalent != null) {
678             report(issue, equivalent, message)
679             return
680         }
681 
682         report(issue, item, message)
683     }
684 
handleRemovednull685     private fun handleRemoved(issue: Issue, item: Item) {
686         if (!item.emit) {
687             // It's a stub; this can happen when analyzing partial APIs
688             // such as a signature file for a library referencing types
689             // from the upstream library dependencies.
690             return
691         }
692 
693         if (base != null) {
694             // We're diffing "overlay" APIs, such as system or test API files,
695             // where the signature files only list a delta from the full, "base" API.
696             // In that case, if an API is promoted from @SystemApi or @TestApi to be
697             // a full part of the API, it will look like a removal; it appeared in the
698             // previous file and not in the new file, but it's not removed, it's just
699             // not a delta anymore.
700             //
701             // For that reason, we also pass in the "base" API in these cases, and when
702             // an item is removed, we also check the full API to see if it's present
703             // there, and if so, this item is not actually deleted.
704             val baseItem = findBaseItem(item)
705             if (baseItem != null && ApiPredicate(ignoreShown = true).test(baseItem)) {
706                 return
707             }
708         }
709 
710         report(issue, item, "Removed ${if (item.deprecated) "deprecated " else ""}${describe(item)}")
711     }
712 
findBaseItemnull713     private fun findBaseItem(
714         item: Item
715     ): Item? {
716         base ?: return null
717 
718         return when (item) {
719             is PackageItem -> base.findPackage(item.qualifiedName())
720             is ClassItem -> base.findClass(item.qualifiedName())
721             is MethodItem -> base.findClass(item.containingClass().qualifiedName())?.findMethod(
722                 item,
723                 includeSuperClasses = true,
724                 includeInterfaces = true
725             )
726             is FieldItem -> base.findClass(item.containingClass().qualifiedName())?.findField(item.name())
727             else -> null
728         }
729     }
730 
addednull731     override fun added(new: PackageItem) {
732         handleAdded(Issues.ADDED_PACKAGE, new)
733     }
734 
addednull735     override fun added(new: ClassItem) {
736         val error = if (new.isInterface()) {
737             Issues.ADDED_INTERFACE
738         } else {
739             Issues.ADDED_CLASS
740         }
741         handleAdded(error, new)
742     }
743 
addednull744     override fun added(new: MethodItem) {
745         // In old signature files, methods inherited from hidden super classes
746         // are not included. An example of this is StringBuilder.setLength.
747         // More details about this are listed in Compatibility.skipInheritedMethods.
748         // We may see these in the codebase but not in the (old) signature files,
749         // so skip these -- they're not really "added".
750         if (new.inheritedFrom != null && comparingWithPartialSignatures) {
751             return
752         }
753 
754         // *Overriding* methods from super classes that are outside the
755         // API is OK (e.g. overriding toString() from java.lang.Object)
756         val superMethods = new.superMethods()
757         for (superMethod in superMethods) {
758             if (superMethod.isFromClassPath()) {
759                 return
760             }
761         }
762 
763         // Do not fail if this "new" method is really an override of an
764         // existing superclass method, but we should fail if this is overriding
765         // an abstract method, because method's abstractness affects how users use it.
766         // See if there's a member from inherited class
767         val inherited = if (new.isConstructor()) {
768             null
769         } else {
770             new.containingClass().findMethod(
771                 new,
772                 includeSuperClasses = true,
773                 includeInterfaces = false
774             )
775         }
776 
777         // Builtin annotation methods: just a difference in signature file
778         if (new.isEnumSyntheticMethod()) {
779             return
780         }
781 
782         // In old signature files, annotation methods are missing! This will show up as an added method.
783         if (new.containingClass().isAnnotationType() && oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1) {
784             return
785         }
786 
787         // In most cases it is not permitted to add a new method to an interface, even with a
788         // default implementation because it could could create ambiguity if client code implements
789         // two interfaces that each now define methods with the same signature.
790         // Annotation types cannot implement other interfaces, however, so it is permitted to add
791         // add new default methods to annotation types.
792         if (new.containingClass().isAnnotationType() && new.hasDefaultValue()) {
793             return
794         }
795 
796         if (inherited == null || inherited == new || !inherited.modifiers.isAbstract()) {
797             val error = if (new.modifiers.isAbstract()) Issues.ADDED_ABSTRACT_METHOD else Issues.ADDED_METHOD
798             handleAdded(error, new)
799         }
800     }
801 
addednull802     override fun added(new: FieldItem) {
803         if (new.inheritedFrom != null && comparingWithPartialSignatures) {
804             return
805         }
806 
807         handleAdded(Issues.ADDED_FIELD, new)
808     }
809 
removednull810     override fun removed(old: PackageItem, from: Item?) {
811         handleRemoved(Issues.REMOVED_PACKAGE, old)
812     }
813 
removednull814     override fun removed(old: ClassItem, from: Item?) {
815         val error = when {
816             old.isInterface() -> Issues.REMOVED_INTERFACE
817             old.deprecated -> Issues.REMOVED_DEPRECATED_CLASS
818             else -> Issues.REMOVED_CLASS
819         }
820 
821         handleRemoved(error, old)
822     }
823 
removednull824     override fun removed(old: MethodItem, from: ClassItem?) {
825         // See if there's a member from inherited class
826         val inherited = if (old.isConstructor()) {
827             null
828         } else {
829             // This can also return self, specially handled below
830             from?.findMethod(
831                 old,
832                 includeSuperClasses = true,
833                 includeInterfaces = from.isInterface()
834             )
835         }
836         if (inherited == null || inherited != old && inherited.isHiddenOrRemoved()) {
837             val error = if (old.deprecated) Issues.REMOVED_DEPRECATED_METHOD else Issues.REMOVED_METHOD
838             handleRemoved(error, old)
839         }
840     }
841 
removednull842     override fun removed(old: FieldItem, from: ClassItem?) {
843         val inherited = from?.findField(
844             old.name(),
845             includeSuperClasses = true,
846             includeInterfaces = from.isInterface()
847         )
848         if (inherited == null) {
849             val error = if (old.deprecated) Issues.REMOVED_DEPRECATED_FIELD else Issues.REMOVED_FIELD
850             handleRemoved(error, old)
851         }
852     }
853 
reportnull854     private fun report(
855         issue: Issue,
856         item: Item,
857         message: String
858     ) {
859         if (reporter.report(issue, item, message) && configuration.getSeverity(issue) == Severity.ERROR) {
860             foundProblems = true
861         }
862     }
863 
864     companion object {
checkCompatibilitynull865         fun checkCompatibility(
866             codebase: Codebase,
867             previous: Codebase,
868             releaseType: ReleaseType,
869             apiType: ApiType,
870             base: Codebase? = null
871         ) {
872             val filter = apiType.getEmitFilter()
873             val checker = CompatibilityCheck(filter, previous, apiType, base, getReporterForReleaseType(releaseType))
874             val issueConfiguration = releaseType.getIssueConfiguration()
875             val previousConfiguration = configuration
876             try {
877                 configuration = issueConfiguration
878                 CodebaseComparator().compare(checker, previous, codebase, filter)
879             } finally {
880                 configuration = previousConfiguration
881             }
882 
883             val message = "Found compatibility problems checking " +
884                 "the ${apiType.displayName} API (${codebase.location}) against the API in ${previous.location}"
885 
886             if (checker.foundProblems) {
887                 throw DriverException(exitCode = -1, stderr = message)
888             }
889         }
890 
getReporterForReleaseTypenull891         private fun getReporterForReleaseType(releaseType: ReleaseType): Reporter = when (releaseType) {
892             ReleaseType.DEV -> options.reporterCompatibilityCurrent
893             ReleaseType.RELEASED -> options.reporterCompatibilityReleased
894         }
895     }
896 }
897