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 package android.signature.cts; 17 18 import android.signature.cts.JDiffClassDescription.JDiffField; 19 import android.signature.cts.ReflectionHelper.DefaultTypeComparator; 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.Field; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.util.Formatter; 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Map; 28 import java.util.Objects; 29 import java.util.Set; 30 31 /** 32 * Checks that the runtime representation of a class matches the API representation of a class. 33 */ 34 public class ApiComplianceChecker extends ApiPresenceChecker { 35 36 /** 37 * A set of field values signatures whose value modifier should be ignored. 38 * 39 * <p>If a field value is intended to be changed to correct its value, that change should be 40 * allowed. The field name is the key of the ignoring map, and a FieldValuePair which is a pair 41 * of the old value and the new value is the value of the ignoring map. 42 * WARNING: Entries should only be added after consulting API council. 43 */ 44 private static class FieldValuePair { 45 private String oldValue; 46 private String newValue; 47 FieldValuePair(String oldValue, String newValue)48 private FieldValuePair(String oldValue, String newValue) { 49 this.oldValue = oldValue; 50 this.newValue = newValue; 51 } 52 }; 53 private static final Map<String, FieldValuePair> IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST = 54 new HashMap<String, FieldValuePair>(); 55 static { 56 // This field value was previously wrong. As the CtsSystemApiSignatureTestCases package 57 // tests both the old and new specifications with both old and new values, this needs to be 58 // ignored. 59 IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put( 60 "android.media.tv.tuner.frontend.FrontendSettings#FEC_28_45(long)", 61 new FieldValuePair("-2147483648", "2147483648")); 62 IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put( 63 "android.media.tv.tuner.frontend.FrontendSettings#FEC_29_45(long)", 64 new FieldValuePair("1", "4294967296")); 65 IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put( 66 "android.media.tv.tuner.frontend.FrontendSettings#FEC_31_45(long)", 67 new FieldValuePair("2", "8589934592")); 68 IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put( 69 "android.media.tv.tuner.frontend.FrontendSettings#FEC_32_45(long)", 70 new FieldValuePair("4", "17179869184")); 71 IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put( 72 "android.media.tv.tuner.frontend.FrontendSettings#FEC_77_90(long)", 73 new FieldValuePair("8", "34359738368")); 74 } 75 76 /** Indicates that the class is an annotation. */ 77 private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000; 78 79 /** Indicates that the class is an enum. */ 80 private static final int CLASS_MODIFIER_ENUM = 0x00004000; 81 82 /** Indicates that the method is a bridge method. */ 83 private static final int METHOD_MODIFIER_BRIDGE = 0x00000040; 84 85 /** Indicates that the method is takes a variable number of arguments. */ 86 private static final int METHOD_MODIFIER_VAR_ARGS = 0x00000080; 87 88 /** Indicates that the method is a synthetic method. */ 89 private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000; 90 91 /** Indicates that a field is an enum value. */ 92 public static final int FIELD_MODIFIER_ENUM_VALUE = 0x00004000; 93 94 private final InterfaceChecker interfaceChecker; 95 ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider)96 public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) { 97 super(classProvider, resultObserver); 98 interfaceChecker = new InterfaceChecker(resultObserver, classProvider); 99 } 100 checkDeferred()101 public void checkDeferred() { 102 interfaceChecker.checkQueued(); 103 } 104 105 @Override checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass)106 protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) { 107 if (JDiffClassDescription.JDiffType.INTERFACE.equals(classDescription.getClassType())) { 108 // Queue the interface for deferred checking. 109 interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass); 110 } 111 112 String reason; 113 if ((reason = checkClassModifiersCompliance(classDescription, runtimeClass)) != null) { 114 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 115 classDescription.getAbsoluteClassName(), 116 String.format("Non-compatible class found when looking for %s - because %s", 117 classDescription.toSignatureString(), reason)); 118 return false; 119 } 120 121 if (!checkClassAnnotationCompliance(classDescription, runtimeClass)) { 122 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 123 classDescription.getAbsoluteClassName(), "Annotation mismatch"); 124 return false; 125 } 126 127 if (!runtimeClass.isAnnotation()) { 128 // check father class 129 if (!checkClassExtendsCompliance(classDescription, runtimeClass)) { 130 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 131 classDescription.getAbsoluteClassName(), 132 "Extends mismatch, expected " + classDescription.getExtendedClass()); 133 return false; 134 } 135 136 // check implements interface 137 if (!checkClassImplementsCompliance(classDescription, runtimeClass)) { 138 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 139 classDescription.getAbsoluteClassName(), 140 "Implements mismatch, expected " + classDescription.getImplInterfaces()); 141 return false; 142 } 143 } 144 return true; 145 } 146 147 /** 148 * Check if the class definition is from a previous API and was neither instantiable nor 149 * extensible through that API. 150 * 151 * <p>Such a class is more flexible in how it can be modified than other classes as there is 152 * no way to either create or extend the class.</p> 153 * 154 * <p>A class that has no constructors in the API cannot be instantiated or extended. Such a 155 * class has a lot more flexibility when it comes to making forwards compatible changes than 156 * other classes. e.g. Normally, a non-final class cannot be made final as that would break any 157 * code that extended the class but if there are no constructors in the API then it is 158 * impossible to extend it through the API so making it final is forwards compatible.</p> 159 * 160 * <p>Similarly, a concrete class cannot normally be made abstract as that would break any code 161 * that attempted to instantiate it but if there are no constructors in the API then it is 162 * impossible to instantiate it so making it abstract is forwards compatible.</p> 163 * 164 * <p>Finally, a non-static class cannot normally be made static (or vice versa) as that would 165 * break any code that attemped to instantiate it but if there are no constructors in the API 166 * then it is impossible to instantiate so changing the static flag is forwards compatible.</p> 167 * 168 * <p>In a similar fashion the abstract and final (but not static) modifier can be added to a 169 * method on this type of class.</p> 170 * 171 * <p>In this case forwards compatible is restricted to compile time and runtime behavior. It 172 * does not cover testing. e.g. making a class that was previously non-final could break tests 173 * that relied on mocking that class. However, that is a non-standard use of the API and so we 174 * are not strictly required to maintain compatibility in that case. It should also only be a 175 * minor issue as most mocking libraries support mocking final classes now.</p> 176 * 177 * @param classDescription a description of a class in an API. 178 */ classIsNotInstantiableOrExtensibleInPreviousApi( JDiffClassDescription classDescription)179 private static boolean classIsNotInstantiableOrExtensibleInPreviousApi( 180 JDiffClassDescription classDescription) { 181 return classDescription.getConstructors().isEmpty() 182 && classDescription.isPreviousApi(); 183 } 184 185 /** 186 * If a modifier (final or abstract) has been removed since the previous API was published then 187 * it is forwards compatible so clear the modifier flag in the previous API modifiers so that it 188 * does not cause a mismatch. 189 * 190 * @param previousModifiers The set of modifiers for the previous API. 191 * @param currentModifiers The set of modifiers for the current implementation class. 192 * @return the normalized previous modifiers. 193 */ normalizePreviousModifiersIfModifierIsRemoved( int previousModifiers, int currentModifiers, int... flags)194 private static int normalizePreviousModifiersIfModifierIsRemoved( 195 int previousModifiers, int currentModifiers, int... flags) { 196 for (int flag : flags) { 197 // If the flag was present in the previous API but is no longer present then the 198 // modifier has been removed. 199 if ((previousModifiers & flag) != 0 && (currentModifiers & flag) == 0) { 200 previousModifiers &= ~flag; 201 } 202 } 203 204 return previousModifiers; 205 } 206 207 /** 208 * If a modifier (final or abstract) has been added since the previous API was published then 209 * this treats it as forwards compatible and clears the modifier flag in the current API 210 * modifiers so that it does not cause a mismatch. 211 * 212 * <p>This must only be called when adding one of the supplied modifiers is forwards compatible, 213 * e.g. when called on a class or methods from a class that returns true for 214 * {@link #classIsNotInstantiableOrExtensibleInPreviousApi(JDiffClassDescription)}.</p> 215 * 216 * @param previousModifiers The set of modifiers for the previous API. 217 * @param currentModifiers The set of modifiers for the current implementation class. 218 * @return the normalized current modifiers. 219 */ normalizeCurrentModifiersIfModifierIsAdded( int previousModifiers, int currentModifiers, int... flags)220 private static int normalizeCurrentModifiersIfModifierIsAdded( 221 int previousModifiers, int currentModifiers, int... flags) { 222 for (int flag : flags) { 223 // If the flag was not present in the previous API but is present then the modifier has 224 // been added. 225 if ((previousModifiers & flag) == 0 && (currentModifiers & flag) != 0) { 226 currentModifiers &= ~flag; 227 } 228 } 229 230 return currentModifiers; 231 } 232 233 /** 234 * Checks if the class under test has compliant modifiers compared to the API. 235 * 236 * @param classDescription a description of a class in an API. 237 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 238 * @return null if modifiers are compliant otherwise a reason why they are not. 239 */ checkClassModifiersCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)240 private static String checkClassModifiersCompliance(JDiffClassDescription classDescription, 241 Class<?> runtimeClass) { 242 int reflectionModifiers = runtimeClass.getModifiers(); 243 int apiModifiers = classDescription.getModifier(); 244 245 // If the api class is an interface then always treat it as abstract. 246 // interfaces are implicitly abstract (JLS 9.1.1.1) 247 if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) { 248 apiModifiers |= Modifier.ABSTRACT; 249 } 250 251 if (classDescription.isAnnotation()) { 252 reflectionModifiers &= ~CLASS_MODIFIER_ANNOTATION; 253 } 254 if (runtimeClass.isInterface()) { 255 reflectionModifiers &= ~(Modifier.INTERFACE); 256 } 257 if (classDescription.isEnumType() && runtimeClass.isEnum()) { 258 reflectionModifiers &= ~CLASS_MODIFIER_ENUM; 259 260 // Most enums are marked as final, however enums that have one or more constants that 261 // override a method from the class cannot be marked as final because those constants 262 // are represented as a subclass. As enum classes cannot be extended (except for its own 263 // constants) there is no benefit in checking final modifier so just ignore them. 264 // 265 // Ditto for abstract. 266 reflectionModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT); 267 apiModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT); 268 } 269 270 if (classDescription.isPreviousApi()) { 271 // If the final and/or abstract modifiers have been removed since the previous API was 272 // published then that is forwards compatible so remove the modifier in the previous API 273 // modifiers so they match the runtime modifiers. 274 apiModifiers = normalizePreviousModifiersIfModifierIsRemoved( 275 apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT); 276 277 if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) { 278 // Adding the final, abstract or static flags to the runtime class is forwards 279 // compatible as the class cannot be instantiated or extended. Clear the flags for 280 // any such added modifier from the current implementation's modifiers so that it 281 // does not cause a mismatch. 282 reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded( 283 apiModifiers, reflectionModifiers, 284 Modifier.FINAL, Modifier.ABSTRACT, Modifier.STATIC); 285 } 286 } 287 288 if ((reflectionModifiers == apiModifiers) 289 && (classDescription.isEnumType() == runtimeClass.isEnum())) { 290 return null; 291 } else { 292 return String.format("modifier mismatch - description (%s), class (%s)", 293 getModifierString(apiModifiers), getModifierString(reflectionModifiers)); 294 } 295 } 296 297 /** 298 * Checks if the class under test is compliant with regards to 299 * annnotations when compared to the API. 300 * 301 * @param classDescription a description of a class in an API. 302 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 303 * @return true if the class is compliant 304 */ checkClassAnnotationCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)305 private static boolean checkClassAnnotationCompliance(JDiffClassDescription classDescription, 306 Class<?> runtimeClass) { 307 if (runtimeClass.isAnnotation()) { 308 // check annotation 309 for (String inter : classDescription.getImplInterfaces()) { 310 if ("java.lang.annotation.Annotation".equals(inter)) { 311 return true; 312 } 313 } 314 return false; 315 } 316 return true; 317 } 318 319 /** 320 * Checks if the class under test extends the proper classes 321 * according to the API. 322 * 323 * @param classDescription a description of a class in an API. 324 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 325 * @return true if the class is compliant. 326 */ checkClassExtendsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)327 private static boolean checkClassExtendsCompliance(JDiffClassDescription classDescription, 328 Class<?> runtimeClass) { 329 // Nothing to check if it doesn't extend anything. 330 if (classDescription.getExtendedClass() != null) { 331 Class<?> superClass = runtimeClass.getSuperclass(); 332 333 while (superClass != null) { 334 if (superClass.getCanonicalName().equals(classDescription.getExtendedClass())) { 335 return true; 336 } 337 superClass = superClass.getSuperclass(); 338 } 339 // Couldn't find a matching superclass. 340 return false; 341 } 342 return true; 343 } 344 345 /** 346 * Checks if the class under test implements the proper interfaces 347 * according to the API. 348 * 349 * @param classDescription a description of a class in an API. 350 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 351 * @return true if the class is compliant 352 */ checkClassImplementsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)353 private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription, 354 Class<?> runtimeClass) { 355 Set<String> interFaceSet = new HashSet<>(); 356 357 addInterfacesToSetByName(runtimeClass, interFaceSet); 358 359 for (String inter : classDescription.getImplInterfaces()) { 360 if (!interFaceSet.contains(inter)) { 361 return false; 362 } 363 } 364 return true; 365 } 366 addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet)367 private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) { 368 Class<?>[] interfaces = runtimeClass.getInterfaces(); 369 for (Class<?> c : interfaces) { 370 interFaceSet.add(c.getCanonicalName()); 371 // Add grandparent interfaces in case the parent interface is hidden. 372 addInterfacesToSetByName(c, interFaceSet); 373 } 374 375 // Add the interfaces that the super class implements as well just in case the super class 376 // is hidden. 377 Class<?> superClass = runtimeClass.getSuperclass(); 378 if (superClass != null) { 379 addInterfacesToSetByName(superClass, interFaceSet); 380 } 381 } 382 383 @Override checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffField fieldDescription, Field field)384 protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, 385 JDiffField fieldDescription, Field field) { 386 int expectedModifiers = fieldDescription.mModifier; 387 int actualModifiers = field.getModifiers(); 388 if (actualModifiers != expectedModifiers) { 389 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 390 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 391 String.format( 392 "Incompatible field modifiers, expected %s, found %s", 393 getModifierString(expectedModifiers), 394 getModifierString(actualModifiers))); 395 } 396 397 String expectedFieldType = fieldDescription.mFieldType; 398 String actualFieldType = ReflectionHelper.typeToString(field.getGenericType()); 399 if (!DefaultTypeComparator.INSTANCE.compare(expectedFieldType, actualFieldType)) { 400 resultObserver.notifyFailure( 401 FailureType.MISMATCH_FIELD, 402 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 403 String.format("Incompatible field type found, expected %s, found %s", 404 expectedFieldType, actualFieldType)); 405 } 406 407 String message = checkFieldValueCompliance(classDescription, fieldDescription, field); 408 if (message != null) { 409 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 410 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 411 message); 412 } 413 } 414 415 private static final int BRIDGE = 0x00000040; 416 private static final int VARARGS = 0x00000080; 417 private static final int SYNTHETIC = 0x00001000; 418 private static final int ANNOTATION = 0x00002000; 419 private static final int ENUM = 0x00004000; 420 private static final int MANDATED = 0x00008000; 421 getModifierString(int modifiers)422 private static String getModifierString(int modifiers) { 423 Formatter formatter = new Formatter(); 424 String m = Modifier.toString(modifiers); 425 formatter.format("<%s", m); 426 String sep = m.isEmpty() ? "" : " "; 427 if ((modifiers & BRIDGE) != 0) { 428 formatter.format("%senum", sep); 429 sep = " "; 430 } 431 if ((modifiers & VARARGS) != 0) { 432 formatter.format("%svarargs", sep); 433 sep = " "; 434 } 435 if ((modifiers & SYNTHETIC) != 0) { 436 formatter.format("%ssynthetic", sep); 437 sep = " "; 438 } 439 if ((modifiers & ANNOTATION) != 0) { 440 formatter.format("%sannotation", sep); 441 sep = " "; 442 } 443 if ((modifiers & ENUM) != 0) { 444 formatter.format("%senum", sep); 445 sep = " "; 446 } 447 if ((modifiers & MANDATED) != 0) { 448 formatter.format("%smandated", sep); 449 } 450 return formatter.format("> (0x%x)", modifiers).toString(); 451 } 452 453 /** 454 * Checks whether the field values are compatible. 455 * 456 * @param apiField The field as defined by the platform API. 457 * @param deviceField The field as defined by the device under test. 458 */ checkFieldValueCompliance( JDiffClassDescription classDescription, JDiffField apiField, Field deviceField)459 private static String checkFieldValueCompliance( 460 JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) { 461 if ((apiField.mModifier & Modifier.FINAL) == 0 || 462 (apiField.mModifier & Modifier.STATIC) == 0) { 463 // Only final static fields can have fixed values. 464 return null; 465 } 466 String apiFieldValue = apiField.getValueString(); 467 if (apiFieldValue == null) { 468 // If we don't define a constant value for it, then it can be anything. 469 return null; 470 } 471 472 // Convert char into a number to match the value returned from device field. The device 473 // field does not 474 if (deviceField.getType() == char.class) { 475 apiFieldValue = convertCharToCanonicalValue(apiFieldValue.charAt(0)); 476 } 477 478 String deviceFieldValue = getFieldValueAsString(deviceField); 479 if (!Objects.equals(apiFieldValue, deviceFieldValue)) { 480 String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName()); 481 if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName) 482 && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals( 483 apiFieldValue) 484 && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals( 485 deviceFieldValue)) { 486 return null; 487 } 488 return String.format("Incorrect field value, expected <%s>, found <%s>", 489 apiFieldValue, deviceFieldValue); 490 491 } 492 493 return null; 494 } 495 getFieldValueAsString(Field deviceField)496 private static String getFieldValueAsString(Field deviceField) { 497 // Some fields may be protected or package-private 498 deviceField.setAccessible(true); 499 try { 500 Class<?> fieldType = deviceField.getType(); 501 if (fieldType == byte.class) { 502 return Byte.toString(deviceField.getByte(null)); 503 } else if (fieldType == char.class) { 504 return convertCharToCanonicalValue(deviceField.getChar(null)); 505 } else if (fieldType == short.class) { 506 return Short.toString(deviceField.getShort(null)); 507 } else if (fieldType == int.class) { 508 return Integer.toString(deviceField.getInt(null)); 509 } else if (fieldType == long.class) { 510 return Long.toString(deviceField.getLong(null)); 511 } else if (fieldType == float.class) { 512 return canonicalizeFloatingPoint( 513 Float.toString(deviceField.getFloat(null))); 514 } else if (fieldType == double.class) { 515 return canonicalizeFloatingPoint( 516 Double.toString(deviceField.getDouble(null))); 517 } else if (fieldType == boolean.class) { 518 return Boolean.toString(deviceField.getBoolean(null)); 519 } else if (fieldType == java.lang.String.class) { 520 return (String) deviceField.get(null); 521 } else { 522 return null; 523 } 524 } catch (IllegalAccessException e) { 525 throw new RuntimeException(e); 526 } 527 } 528 convertCharToCanonicalValue(char c)529 private static String convertCharToCanonicalValue(char c) { 530 return String.format("'%c' (0x%x)", c, (int) c); 531 } 532 533 /** 534 * Canonicalize the string representation of floating point numbers. 535 * 536 * This needs to be kept in sync with the doclava canonicalization. 537 */ canonicalizeFloatingPoint(String val)538 private static String canonicalizeFloatingPoint(String val) { 539 switch (val) { 540 case "Infinity": 541 case "-Infinity": 542 case "NaN": 543 return val; 544 } 545 546 if (val.indexOf('E') != -1) { 547 return val; 548 } 549 550 // 1.0 is the only case where a trailing "0" is allowed. 551 // 1.00 is canonicalized as 1.0. 552 int i = val.length() - 1; 553 int d = val.indexOf('.'); 554 while (i >= d + 2 && val.charAt(i) == '0') { 555 val = val.substring(0, i--); 556 } 557 return val; 558 } 559 560 @Override checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor)561 protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, 562 JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) { 563 if (ctor.isVarArgs()) {// some method's parameter are variable args 564 ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS; 565 } 566 if (ctor.getModifiers() != ctorDescription.mModifier) { 567 resultObserver.notifyFailure( 568 FailureType.MISMATCH_METHOD, 569 ctorDescription.toReadableString(classDescription.getAbsoluteClassName()), 570 "Non-compatible method found when looking for " + 571 ctorDescription.toSignatureString()); 572 } 573 } 574 575 @Override checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffMethod methodDescription, Method method)576 protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, 577 JDiffClassDescription.JDiffMethod methodDescription, Method method) { 578 // FIXME: A workaround to fix the final mismatch on enumeration 579 if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) { 580 return; 581 } 582 583 String reason; 584 if ((reason = areMethodsModifierCompatible( 585 classDescription, methodDescription, method)) != null) { 586 resultObserver.notifyFailure(FailureType.MISMATCH_METHOD, 587 methodDescription.toReadableString(classDescription.getAbsoluteClassName()), 588 String.format("Non-compatible method found when looking for %s - because %s", 589 methodDescription.toSignatureString(), reason)); 590 } 591 } 592 593 /** 594 * Checks to ensure that the modifiers value for two methods are compatible. 595 * 596 * Allowable differences are: 597 * - the native modifier is ignored 598 * 599 * @param classDescription a description of a class in an API. 600 * @param apiMethod the method read from the api file. 601 * @param reflectedMethod the method found via reflection. 602 * @return null if the method modifiers are compatible otherwise the reason why not. 603 */ areMethodsModifierCompatible( JDiffClassDescription classDescription, JDiffClassDescription.JDiffMethod apiMethod, Method reflectedMethod)604 private static String areMethodsModifierCompatible( 605 JDiffClassDescription classDescription, 606 JDiffClassDescription.JDiffMethod apiMethod, 607 Method reflectedMethod) { 608 609 // Mask off NATIVE since it is a don't care. 610 // Mask off SYNCHRONIZED since it is not considered API significant (b/112626813) 611 // Mask off STRICT as it has no effect (b/26082535) 612 // Mask off SYNTHETIC, VARARGS and BRIDGE as they are not represented in the API. 613 int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT | 614 METHOD_MODIFIER_SYNTHETIC | METHOD_MODIFIER_VAR_ARGS | METHOD_MODIFIER_BRIDGE); 615 int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods; 616 int apiModifiers = apiMethod.mModifier & ~ignoredMods; 617 618 // We can ignore FINAL for classes 619 if ((classDescription.getModifier() & Modifier.FINAL) != 0) { 620 reflectionModifiers &= ~Modifier.FINAL; 621 apiModifiers &= ~Modifier.FINAL; 622 } 623 624 String genericString = reflectedMethod.toGenericString(); 625 if (classDescription.isPreviousApi()) { 626 // If the final and/or abstract modifiers have been removed since the previous API was 627 // published then that is forwards compatible so remove the modifier in the previous API 628 // modifiers so they match the runtime modifiers. 629 apiModifiers = normalizePreviousModifiersIfModifierIsRemoved( 630 apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT); 631 632 if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) { 633 // Adding the final, or abstract flags to the runtime method is forwards compatible 634 // as the class cannot be instantiated or extended. Clear the flags for any such 635 // added modifier from the current implementation's modifiers so that it does not 636 // cause a mismatch. 637 reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded( 638 apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT); 639 } 640 } 641 642 if (reflectionModifiers == apiModifiers) { 643 return null; 644 } else { 645 return String.format("modifier mismatch - description (%s), method (%s), for %s", 646 getModifierString(apiModifiers), getModifierString(reflectionModifiers), genericString); 647 } 648 } 649 addBaseClass(JDiffClassDescription classDescription)650 public void addBaseClass(JDiffClassDescription classDescription) { 651 // Keep track of all the base interfaces that may by extended. 652 if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) { 653 try { 654 Class<?> runtimeClass = 655 ReflectionHelper.findMatchingClass(classDescription, classProvider); 656 interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass); 657 } catch (ClassNotFoundException e) { 658 // Do nothing. 659 } 660 } 661 } 662 } 663