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