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