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