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