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 (classDescription.isPreviousApi()) { 276 // If the final and/or abstract modifiers have been removed since the previous API was 277 // published then that is forwards compatible so remove the modifier in the previous API 278 // modifiers so they match the runtime modifiers. 279 apiModifiers = 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 = normalizeCurrentModifiersIfModifierIsAdded( 288 apiModifiers, reflectionModifiers, 289 Modifier.FINAL, Modifier.ABSTRACT, Modifier.STATIC); 290 } 291 } 292 293 if ((reflectionModifiers == apiModifiers) 294 && (classDescription.isEnumType() == runtimeClass.isEnum())) { 295 return null; 296 } else { 297 return String.format("modifier mismatch - description (%s), class (%s)", 298 getModifierString(apiModifiers), getModifierString(reflectionModifiers)); 299 } 300 } 301 302 /** 303 * Checks if the class under test is compliant with regards to 304 * annnotations when compared to the API. 305 * 306 * @param classDescription a description of a class in an API. 307 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 308 * @return true if the class is compliant 309 */ checkClassAnnotationCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)310 private static boolean checkClassAnnotationCompliance(JDiffClassDescription classDescription, 311 Class<?> runtimeClass) { 312 if (runtimeClass.isAnnotation()) { 313 // check annotation 314 for (String inter : classDescription.getImplInterfaces()) { 315 if ("java.lang.annotation.Annotation".equals(inter)) { 316 return true; 317 } 318 } 319 return false; 320 } 321 return true; 322 } 323 324 /** 325 * Checks if the class under test extends the proper classes 326 * according to the API. 327 * 328 * @param classDescription a description of a class in an API. 329 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 330 * @return true if the class is compliant. 331 */ checkClassExtendsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)332 private static boolean checkClassExtendsCompliance(JDiffClassDescription classDescription, 333 Class<?> runtimeClass) { 334 // Nothing to check if it doesn't extend anything. 335 if (classDescription.getExtendedClass() != null) { 336 Class<?> superClass = runtimeClass.getSuperclass(); 337 338 while (superClass != null) { 339 if (superClass.getCanonicalName().equals(classDescription.getExtendedClass())) { 340 return true; 341 } 342 superClass = superClass.getSuperclass(); 343 } 344 // Couldn't find a matching superclass. 345 return false; 346 } 347 return true; 348 } 349 350 /** 351 * Checks if the class under test implements the proper interfaces 352 * according to the API. 353 * 354 * @param classDescription a description of a class in an API. 355 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 356 * @return true if the class is compliant 357 */ checkClassImplementsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)358 private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription, 359 Class<?> runtimeClass) { 360 Set<String> interFaceSet = new HashSet<>(); 361 362 addInterfacesToSetByName(runtimeClass, interFaceSet); 363 364 for (String inter : classDescription.getImplInterfaces()) { 365 if (!interFaceSet.contains(inter)) { 366 return false; 367 } 368 } 369 return true; 370 } 371 addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet)372 private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) { 373 Class<?>[] interfaces = runtimeClass.getInterfaces(); 374 for (Class<?> c : interfaces) { 375 interFaceSet.add(c.getCanonicalName()); 376 // Add grandparent interfaces in case the parent interface is hidden. 377 addInterfacesToSetByName(c, interFaceSet); 378 } 379 380 // Add the interfaces that the super class implements as well just in case the super class 381 // is hidden. 382 Class<?> superClass = runtimeClass.getSuperclass(); 383 if (superClass != null) { 384 addInterfacesToSetByName(superClass, interFaceSet); 385 } 386 } 387 388 @Override checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffField fieldDescription, Field field)389 protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, 390 JDiffField fieldDescription, Field field) { 391 int expectedModifiers = fieldDescription.mModifier; 392 int actualModifiers = field.getModifiers(); 393 if (actualModifiers != expectedModifiers) { 394 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 395 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 396 String.format( 397 "Incompatible field modifiers, expected %s, found %s", 398 getModifierString(expectedModifiers), 399 getModifierString(actualModifiers))); 400 } 401 402 String expectedFieldType = fieldDescription.mFieldType; 403 String actualFieldType = ReflectionHelper.typeToString(field.getGenericType()); 404 if (!DefaultTypeComparator.INSTANCE.compare(expectedFieldType, actualFieldType)) { 405 resultObserver.notifyFailure( 406 FailureType.MISMATCH_FIELD, 407 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 408 String.format("Incompatible field type found, expected %s, found %s", 409 expectedFieldType, actualFieldType)); 410 } 411 412 String message = checkFieldValueCompliance(classDescription, fieldDescription, field); 413 if (message != null) { 414 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 415 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 416 message); 417 } 418 } 419 420 private static final int BRIDGE = 0x00000040; 421 private static final int VARARGS = 0x00000080; 422 private static final int SYNTHETIC = 0x00001000; 423 private static final int ANNOTATION = 0x00002000; 424 private static final int ENUM = 0x00004000; 425 private static final int MANDATED = 0x00008000; 426 getModifierString(int modifiers)427 private static String getModifierString(int modifiers) { 428 Formatter formatter = new Formatter(); 429 String m = Modifier.toString(modifiers); 430 formatter.format("<%s", m); 431 String sep = m.isEmpty() ? "" : " "; 432 if ((modifiers & BRIDGE) != 0) { 433 formatter.format("%senum", sep); 434 sep = " "; 435 } 436 if ((modifiers & VARARGS) != 0) { 437 formatter.format("%svarargs", sep); 438 sep = " "; 439 } 440 if ((modifiers & SYNTHETIC) != 0) { 441 formatter.format("%ssynthetic", sep); 442 sep = " "; 443 } 444 if ((modifiers & ANNOTATION) != 0) { 445 formatter.format("%sannotation", sep); 446 sep = " "; 447 } 448 if ((modifiers & ENUM) != 0) { 449 formatter.format("%senum", sep); 450 sep = " "; 451 } 452 if ((modifiers & MANDATED) != 0) { 453 formatter.format("%smandated", sep); 454 } 455 return formatter.format("> (0x%x)", modifiers).toString(); 456 } 457 458 /** 459 * Checks whether the field values are compatible. 460 * 461 * @param apiField The field as defined by the platform API. 462 * @param deviceField The field as defined by the device under test. 463 */ checkFieldValueCompliance( JDiffClassDescription classDescription, JDiffField apiField, Field deviceField)464 private static String checkFieldValueCompliance( 465 JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) { 466 if ((apiField.mModifier & Modifier.FINAL) == 0 || 467 (apiField.mModifier & Modifier.STATIC) == 0) { 468 // Only final static fields can have fixed values. 469 return null; 470 } 471 String apiFieldValue = apiField.getValueString(); 472 if (apiFieldValue == null) { 473 // If we don't define a constant value for it, then it can be anything. 474 return null; 475 } 476 477 // Convert char into a number to match the value returned from device field. The device 478 // field does not 479 if (deviceField.getType() == char.class) { 480 apiFieldValue = convertCharToCanonicalValue(apiFieldValue.charAt(0)); 481 } 482 483 String deviceFieldValue = getFieldValueAsString(deviceField); 484 if (!Objects.equals(apiFieldValue, deviceFieldValue)) { 485 String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName()); 486 if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName) 487 && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals( 488 apiFieldValue) 489 && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals( 490 deviceFieldValue)) { 491 return null; 492 } 493 return String.format("Incorrect field value, expected <%s>, found <%s>", 494 apiFieldValue, deviceFieldValue); 495 496 } 497 498 return null; 499 } 500 getFieldValueAsString(Field deviceField)501 private static String getFieldValueAsString(Field deviceField) { 502 // Some fields may be protected or package-private 503 deviceField.setAccessible(true); 504 try { 505 Class<?> fieldType = deviceField.getType(); 506 if (fieldType == byte.class) { 507 return Byte.toString(deviceField.getByte(null)); 508 } else if (fieldType == char.class) { 509 return convertCharToCanonicalValue(deviceField.getChar(null)); 510 } else if (fieldType == short.class) { 511 return Short.toString(deviceField.getShort(null)); 512 } else if (fieldType == int.class) { 513 return Integer.toString(deviceField.getInt(null)); 514 } else if (fieldType == long.class) { 515 return Long.toString(deviceField.getLong(null)); 516 } else if (fieldType == float.class) { 517 return canonicalizeFloatingPoint( 518 Float.toString(deviceField.getFloat(null))); 519 } else if (fieldType == double.class) { 520 return canonicalizeFloatingPoint( 521 Double.toString(deviceField.getDouble(null))); 522 } else if (fieldType == boolean.class) { 523 return Boolean.toString(deviceField.getBoolean(null)); 524 } else if (fieldType == java.lang.String.class) { 525 return (String) deviceField.get(null); 526 } else { 527 return null; 528 } 529 } catch (IllegalAccessException e) { 530 throw new RuntimeException(e); 531 } 532 } 533 convertCharToCanonicalValue(char c)534 private static String convertCharToCanonicalValue(char c) { 535 return String.format("'%c' (0x%x)", c, (int) c); 536 } 537 538 /** 539 * Canonicalize the string representation of floating point numbers. 540 * 541 * This needs to be kept in sync with the doclava canonicalization. 542 */ canonicalizeFloatingPoint(String val)543 private static String canonicalizeFloatingPoint(String val) { 544 switch (val) { 545 case "Infinity": 546 case "-Infinity": 547 case "NaN": 548 return val; 549 } 550 551 if (val.indexOf('E') != -1) { 552 return val; 553 } 554 555 // 1.0 is the only case where a trailing "0" is allowed. 556 // 1.00 is canonicalized as 1.0. 557 int i = val.length() - 1; 558 int d = val.indexOf('.'); 559 while (i >= d + 2 && val.charAt(i) == '0') { 560 val = val.substring(0, i--); 561 } 562 return val; 563 } 564 565 @Override checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor)566 protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, 567 JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) { 568 if (ctor.isVarArgs()) {// some method's parameter are variable args 569 ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS; 570 } 571 if (ctor.getModifiers() != ctorDescription.mModifier) { 572 resultObserver.notifyFailure( 573 FailureType.MISMATCH_METHOD, 574 ctorDescription.toReadableString(classDescription.getAbsoluteClassName()), 575 "Non-compatible method found when looking for " + 576 ctorDescription.toSignatureString()); 577 } 578 } 579 580 @Override checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffMethod methodDescription, Method method)581 protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, 582 JDiffClassDescription.JDiffMethod methodDescription, Method method) { 583 // FIXME: A workaround to fix the final mismatch on enumeration 584 if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) { 585 return; 586 } 587 588 String reason; 589 if ((reason = areMethodsModifierCompatible( 590 classDescription, methodDescription, method)) != null) { 591 resultObserver.notifyFailure(FailureType.MISMATCH_METHOD, 592 methodDescription.toReadableString(classDescription.getAbsoluteClassName()), 593 String.format("Non-compatible method found when looking for %s - because %s", 594 methodDescription.toSignatureString(), reason)); 595 } 596 } 597 598 /** 599 * Checks to ensure that the modifiers value for two methods are compatible. 600 * 601 * Allowable differences are: 602 * - the native modifier is ignored 603 * 604 * @param classDescription a description of a class in an API. 605 * @param apiMethod the method read from the api file. 606 * @param reflectedMethod the method found via reflection. 607 * @return null if the method modifiers are compatible otherwise the reason why not. 608 */ areMethodsModifierCompatible( JDiffClassDescription classDescription, JDiffClassDescription.JDiffMethod apiMethod, Method reflectedMethod)609 private static String areMethodsModifierCompatible( 610 JDiffClassDescription classDescription, 611 JDiffClassDescription.JDiffMethod apiMethod, 612 Method reflectedMethod) { 613 614 // Mask off NATIVE since it is a don't care. 615 // Mask off SYNCHRONIZED since it is not considered API significant (b/112626813) 616 // Mask off STRICT as it has no effect (b/26082535) 617 // Mask off SYNTHETIC, VARARGS and BRIDGE as they are not represented in the API. 618 int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT | 619 METHOD_MODIFIER_SYNTHETIC | METHOD_MODIFIER_VAR_ARGS | METHOD_MODIFIER_BRIDGE); 620 int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods; 621 int apiModifiers = apiMethod.mModifier & ~ignoredMods; 622 623 // We can ignore FINAL for classes 624 if ((classDescription.getModifier() & Modifier.FINAL) != 0) { 625 reflectionModifiers &= ~Modifier.FINAL; 626 apiModifiers &= ~Modifier.FINAL; 627 } 628 629 String genericString = reflectedMethod.toGenericString(); 630 if (classDescription.isPreviousApi()) { 631 // If the final and/or abstract modifiers have been removed since the previous API was 632 // published then that is forwards compatible so remove the modifier in the previous API 633 // modifiers so they match the runtime modifiers. 634 apiModifiers = normalizePreviousModifiersIfModifierIsRemoved( 635 apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT); 636 637 if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) { 638 // Adding the final, or abstract flags to the runtime method is forwards compatible 639 // as the class cannot be instantiated or extended. Clear the flags for any such 640 // added modifier from the current implementation's modifiers so that it does not 641 // cause a mismatch. 642 reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded( 643 apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT); 644 } 645 } 646 647 if (reflectionModifiers == apiModifiers) { 648 return null; 649 } else { 650 return String.format("modifier mismatch - description (%s), method (%s), for %s", 651 getModifierString(apiModifiers), getModifierString(reflectionModifiers), genericString); 652 } 653 } 654 addBaseClass(JDiffClassDescription classDescription)655 public void addBaseClass(JDiffClassDescription classDescription) { 656 // Keep track of all the base interfaces that may by extended. 657 if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) { 658 try { 659 Class<?> runtimeClass = 660 ReflectionHelper.findMatchingClass(classDescription, classProvider); 661 if (runtimeClass == null) { 662 // Do not treat a missing class from a base API as an error. While it is an 663 // error it will be caught in the test for the base API so there is no point in 664 // having this test fail too as it will just create toil for developers and 665 // testers. Log a message just in case the missing class causes other test 666 // failures. 667 LogHelper.loge("Classloader is unable to find base API class " 668 + classDescription.getAbsoluteClassName(), null); 669 } else { 670 interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass); 671 } 672 } catch (ClassNotFoundException e) { 673 // Do nothing. 674 } 675 } 676 } 677 } 678