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