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