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