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