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