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