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.HashSet; 26 import java.util.Objects; 27 import java.util.Set; 28 29 /** 30 * Checks that the runtime representation of a class matches the API representation of a class. 31 */ 32 public class ApiComplianceChecker extends ApiPresenceChecker { 33 34 /** 35 * A set of method signatures whose abstract modifier should be ignored. 36 * 37 * <p>If a class is not intended to be created or extended by application developers and all 38 * instances are created and supplied by Android itself then the abstract modifier has no 39 * impact on runtime compatibility. 40 */ 41 private static final Set<String> IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST = new HashSet<>(); 42 static { 43 // This method was previously abstract and is now not abstract. As the 44 // CtsSystemApiSignatureTestCases package tests both the old and new specifications, with 45 // and without the abstract modifier this needs to ignore the abstract modifier. 46 IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST.add( 47 "public int android.service.euicc.EuiccService.onDownloadSubscription(" 48 + "int,android.telephony.euicc.DownloadableSubscription,boolean,boolean)"); 49 } 50 51 /** Indicates that the class is an annotation. */ 52 private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000; 53 54 /** Indicates that the class is an enum. */ 55 private static final int CLASS_MODIFIER_ENUM = 0x00004000; 56 57 /** Indicates that the method is a bridge method. */ 58 private static final int METHOD_MODIFIER_BRIDGE = 0x00000040; 59 60 /** Indicates that the method is takes a variable number of arguments. */ 61 private static final int METHOD_MODIFIER_VAR_ARGS = 0x00000080; 62 63 /** Indicates that the method is a synthetic method. */ 64 private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000; 65 66 /** Indicates that a field is an enum value. */ 67 public static final int FIELD_MODIFIER_ENUM_VALUE = 0x00004000; 68 69 private final InterfaceChecker interfaceChecker; 70 ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider)71 public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) { 72 super(classProvider, resultObserver); 73 interfaceChecker = new InterfaceChecker(resultObserver, classProvider); 74 } 75 checkDeferred()76 public void checkDeferred() { 77 interfaceChecker.checkQueued(); 78 } 79 80 @Override checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass)81 protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) { 82 if (JDiffClassDescription.JDiffType.INTERFACE.equals(classDescription.getClassType())) { 83 // Queue the interface for deferred checking. 84 interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass); 85 } 86 87 String reason; 88 if ((reason = checkClassModifiersCompliance(classDescription, runtimeClass)) != null) { 89 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 90 classDescription.getAbsoluteClassName(), 91 String.format("Non-compatible class found when looking for %s - because %s", 92 classDescription.toSignatureString(), reason)); 93 return false; 94 } 95 96 if (!checkClassAnnotationCompliance(classDescription, runtimeClass)) { 97 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 98 classDescription.getAbsoluteClassName(), "Annotation mismatch"); 99 return false; 100 } 101 102 if (!runtimeClass.isAnnotation()) { 103 // check father class 104 if (!checkClassExtendsCompliance(classDescription, runtimeClass)) { 105 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 106 classDescription.getAbsoluteClassName(), 107 "Extends mismatch, expected " + classDescription.getExtendedClass()); 108 return false; 109 } 110 111 // check implements interface 112 if (!checkClassImplementsCompliance(classDescription, runtimeClass)) { 113 resultObserver.notifyFailure(FailureType.mismatch(classDescription), 114 classDescription.getAbsoluteClassName(), 115 "Implements mismatch, expected " + classDescription.getImplInterfaces()); 116 return false; 117 } 118 } 119 return true; 120 } 121 122 /** 123 * Check if it is allowed that a class is in previous system Api and changed to abstract class 124 * in current API. 125 * @param classDescription a description of a class in an API. 126 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 127 * @return true if the change is allowed. 128 */ isAllowedClassAbstractionFromPreviousSystemApi( JDiffClassDescription classDescription, Class<?> runtimeClass)129 private static boolean isAllowedClassAbstractionFromPreviousSystemApi( 130 JDiffClassDescription classDescription, Class<?> runtimeClass) { 131 // Allow a class that was previously final and had no visible constructors, 132 // (so could not be instantiated or extended) to be changed to an abstract class. 133 return classDescription.getConstructors().isEmpty() 134 && (classDescription.getModifier() & Modifier.FINAL) != 0 135 && (classDescription.getModifier() & Modifier.ABSTRACT) == 0 136 && classDescription.isPreviousApi() 137 && (runtimeClass.getModifiers() & Modifier.ABSTRACT) != 0; 138 } 139 140 /** 141 * Checks if the class under test has compliant modifiers compared to the API. 142 * 143 * @param classDescription a description of a class in an API. 144 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 145 * @return null if modifiers are compliant otherwise a reason why they are not. 146 */ checkClassModifiersCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)147 private static String checkClassModifiersCompliance(JDiffClassDescription classDescription, 148 Class<?> runtimeClass) { 149 int reflectionModifiers = runtimeClass.getModifiers(); 150 int apiModifiers = classDescription.getModifier(); 151 152 // If the api class isn't abstract 153 if (((apiModifiers & Modifier.ABSTRACT) == 0) && 154 // but the reflected class is 155 ((reflectionModifiers & Modifier.ABSTRACT) != 0) && 156 // interfaces are implicitly abstract (JLS 9.1.1.1) 157 classDescription.getClassType() != JDiffClassDescription.JDiffType.INTERFACE && 158 // and it isn't an enum 159 !classDescription.isEnumType() && 160 // and it isn't allowed previous api final class with no visible ctor 161 !isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass)) { 162 // that is a problem 163 return "description is abstract but class is not and is not an enum"; 164 } 165 // ABSTRACT check passed, so mask off ABSTRACT 166 reflectionModifiers &= ~Modifier.ABSTRACT; 167 apiModifiers &= ~Modifier.ABSTRACT; 168 169 if (classDescription.isAnnotation()) { 170 reflectionModifiers &= ~CLASS_MODIFIER_ANNOTATION; 171 } 172 if (runtimeClass.isInterface()) { 173 reflectionModifiers &= ~(Modifier.INTERFACE); 174 } 175 if (classDescription.isEnumType() && runtimeClass.isEnum()) { 176 reflectionModifiers &= ~CLASS_MODIFIER_ENUM; 177 178 // Most enums are marked as final, however enums that have one or more constants that 179 // override a method from the class cannot be marked as final because those constants 180 // are represented as a subclass. As enum classes cannot be extended (except for its own 181 // constants) there is no benefit in checking final modifier so just ignore them. 182 reflectionModifiers &= ~Modifier.FINAL; 183 apiModifiers &= ~Modifier.FINAL; 184 } 185 186 // Allow previous final API to be changed to abstract or static, and other modifiers should 187 // not be changed. 188 boolean isAllowedPreviousApiModifierChange = 189 isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass) 190 && (apiModifiers & ~Modifier.FINAL) != 0 191 && (reflectionModifiers & ~(Modifier.ABSTRACT | Modifier.STATIC)) 192 == (apiModifiers & ~Modifier.FINAL); 193 194 if ((reflectionModifiers == apiModifiers) 195 && (classDescription.isEnumType() == runtimeClass.isEnum()) 196 || isAllowedPreviousApiModifierChange) { 197 return null; 198 } else { 199 return String.format("modifier mismatch - description (%s), class (%s)", 200 getModifierString(apiModifiers), getModifierString(reflectionModifiers)); 201 } 202 } 203 204 /** 205 * Checks if the class under test is compliant with regards to 206 * annnotations when compared to the API. 207 * 208 * @param classDescription a description of a class in an API. 209 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 210 * @return true if the class is compliant 211 */ checkClassAnnotationCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)212 private static boolean checkClassAnnotationCompliance(JDiffClassDescription classDescription, 213 Class<?> runtimeClass) { 214 if (runtimeClass.isAnnotation()) { 215 // check annotation 216 for (String inter : classDescription.getImplInterfaces()) { 217 if ("java.lang.annotation.Annotation".equals(inter)) { 218 return true; 219 } 220 } 221 return false; 222 } 223 return true; 224 } 225 226 /** 227 * Checks if the class under test extends the proper classes 228 * according to the API. 229 * 230 * @param classDescription a description of a class in an API. 231 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 232 * @return true if the class is compliant. 233 */ checkClassExtendsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)234 private static boolean checkClassExtendsCompliance(JDiffClassDescription classDescription, 235 Class<?> runtimeClass) { 236 // Nothing to check if it doesn't extend anything. 237 if (classDescription.getExtendedClass() != null) { 238 Class<?> superClass = runtimeClass.getSuperclass(); 239 240 while (superClass != null) { 241 if (superClass.getCanonicalName().equals(classDescription.getExtendedClass())) { 242 return true; 243 } 244 superClass = superClass.getSuperclass(); 245 } 246 // Couldn't find a matching superclass. 247 return false; 248 } 249 return true; 250 } 251 252 /** 253 * Checks if the class under test implements the proper interfaces 254 * according to the API. 255 * 256 * @param classDescription a description of a class in an API. 257 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 258 * @return true if the class is compliant 259 */ checkClassImplementsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)260 private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription, 261 Class<?> runtimeClass) { 262 Set<String> interFaceSet = new HashSet<>(); 263 264 addInterfacesToSetByName(runtimeClass, interFaceSet); 265 266 for (String inter : classDescription.getImplInterfaces()) { 267 if (!interFaceSet.contains(inter)) { 268 return false; 269 } 270 } 271 return true; 272 } 273 addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet)274 private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) { 275 Class<?>[] interfaces = runtimeClass.getInterfaces(); 276 for (Class<?> c : interfaces) { 277 interFaceSet.add(c.getCanonicalName()); 278 // Add grandparent interfaces in case the parent interface is hidden. 279 addInterfacesToSetByName(c, interFaceSet); 280 } 281 282 // Add the interfaces that the super class implements as well just in case the super class 283 // is hidden. 284 Class<?> superClass = runtimeClass.getSuperclass(); 285 if (superClass != null) { 286 addInterfacesToSetByName(superClass, interFaceSet); 287 } 288 } 289 290 @Override checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffField fieldDescription, Field field)291 protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, 292 JDiffField fieldDescription, Field field) { 293 int expectedModifiers = fieldDescription.mModifier; 294 int actualModifiers = field.getModifiers(); 295 if (actualModifiers != expectedModifiers) { 296 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 297 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 298 String.format( 299 "Incompatible field modifiers, expected %s, found %s", 300 getModifierString(expectedModifiers), 301 getModifierString(actualModifiers))); 302 } 303 304 String expectedFieldType = fieldDescription.mFieldType; 305 String actualFieldType = ReflectionHelper.typeToString(field.getGenericType()); 306 if (!DefaultTypeComparator.INSTANCE.compare(expectedFieldType, actualFieldType)) { 307 resultObserver.notifyFailure( 308 FailureType.MISMATCH_FIELD, 309 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 310 String.format("Incompatible field type found, expected %s, found %s", 311 expectedFieldType, actualFieldType)); 312 } 313 314 String message = checkFieldValueCompliance(fieldDescription, field); 315 if (message != null) { 316 resultObserver.notifyFailure(FailureType.MISMATCH_FIELD, 317 fieldDescription.toReadableString(classDescription.getAbsoluteClassName()), 318 message); 319 } 320 } 321 322 private static final int BRIDGE = 0x00000040; 323 private static final int VARARGS = 0x00000080; 324 private static final int SYNTHETIC = 0x00001000; 325 private static final int ANNOTATION = 0x00002000; 326 private static final int ENUM = 0x00004000; 327 private static final int MANDATED = 0x00008000; 328 getModifierString(int modifiers)329 private static String getModifierString(int modifiers) { 330 Formatter formatter = new Formatter(); 331 String m = Modifier.toString(modifiers); 332 formatter.format("<%s", m); 333 String sep = m.isEmpty() ? "" : " "; 334 if ((modifiers & BRIDGE) != 0) { 335 formatter.format("%senum", sep); 336 sep = " "; 337 } 338 if ((modifiers & VARARGS) != 0) { 339 formatter.format("%svarargs", sep); 340 sep = " "; 341 } 342 if ((modifiers & SYNTHETIC) != 0) { 343 formatter.format("%ssynthetic", sep); 344 sep = " "; 345 } 346 if ((modifiers & ANNOTATION) != 0) { 347 formatter.format("%sannotation", sep); 348 sep = " "; 349 } 350 if ((modifiers & ENUM) != 0) { 351 formatter.format("%senum", sep); 352 sep = " "; 353 } 354 if ((modifiers & MANDATED) != 0) { 355 formatter.format("%smandated", sep); 356 } 357 return formatter.format("> (0x%x)", modifiers).toString(); 358 } 359 360 /** 361 * Checks whether the field values are compatible. 362 * 363 * @param apiField The field as defined by the platform API. 364 * @param deviceField The field as defined by the device under test. 365 */ checkFieldValueCompliance(JDiffField apiField, Field deviceField)366 private static String checkFieldValueCompliance(JDiffField apiField, Field deviceField) { 367 if ((apiField.mModifier & Modifier.FINAL) == 0 || 368 (apiField.mModifier & Modifier.STATIC) == 0) { 369 // Only final static fields can have fixed values. 370 return null; 371 } 372 String apiFieldValue = apiField.getValueString(); 373 if (apiFieldValue == null) { 374 // If we don't define a constant value for it, then it can be anything. 375 return null; 376 } 377 378 // Convert char into a number to match the value returned from device field. The device 379 // field does not 380 if (deviceField.getType() == char.class) { 381 apiFieldValue = convertCharToCanonicalValue(apiFieldValue.charAt(0)); 382 } 383 384 String deviceFieldValue = getFieldValueAsString(deviceField); 385 if (!Objects.equals(apiFieldValue, deviceFieldValue)) { 386 return String.format("Incorrect field value, expected <%s>, found <%s>", 387 apiFieldValue, deviceFieldValue); 388 389 } 390 391 return null; 392 } 393 getFieldValueAsString(Field deviceField)394 private static String getFieldValueAsString(Field deviceField) { 395 // Some fields may be protected or package-private 396 deviceField.setAccessible(true); 397 try { 398 Class<?> fieldType = deviceField.getType(); 399 if (fieldType == byte.class) { 400 return Byte.toString(deviceField.getByte(null)); 401 } else if (fieldType == char.class) { 402 return convertCharToCanonicalValue(deviceField.getChar(null)); 403 } else if (fieldType == short.class) { 404 return Short.toString(deviceField.getShort(null)); 405 } else if (fieldType == int.class) { 406 return Integer.toString(deviceField.getInt(null)); 407 } else if (fieldType == long.class) { 408 return Long.toString(deviceField.getLong(null)); 409 } else if (fieldType == float.class) { 410 return canonicalizeFloatingPoint( 411 Float.toString(deviceField.getFloat(null))); 412 } else if (fieldType == double.class) { 413 return canonicalizeFloatingPoint( 414 Double.toString(deviceField.getDouble(null))); 415 } else if (fieldType == boolean.class) { 416 return Boolean.toString(deviceField.getBoolean(null)); 417 } else if (fieldType == java.lang.String.class) { 418 return (String) deviceField.get(null); 419 } else { 420 return null; 421 } 422 } catch (IllegalAccessException e) { 423 throw new RuntimeException(e); 424 } 425 } 426 convertCharToCanonicalValue(char c)427 private static String convertCharToCanonicalValue(char c) { 428 return String.format("'%c' (0x%x)", c, (int) c); 429 } 430 431 /** 432 * Canonicalize the string representation of floating point numbers. 433 * 434 * This needs to be kept in sync with the doclava canonicalization. 435 */ canonicalizeFloatingPoint(String val)436 private static String canonicalizeFloatingPoint(String val) { 437 switch (val) { 438 case "Infinity": 439 case "-Infinity": 440 case "NaN": 441 return val; 442 } 443 444 if (val.indexOf('E') != -1) { 445 return val; 446 } 447 448 // 1.0 is the only case where a trailing "0" is allowed. 449 // 1.00 is canonicalized as 1.0. 450 int i = val.length() - 1; 451 int d = val.indexOf('.'); 452 while (i >= d + 2 && val.charAt(i) == '0') { 453 val = val.substring(0, i--); 454 } 455 return val; 456 } 457 458 @Override checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor)459 protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, 460 JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) { 461 if (ctor.isVarArgs()) {// some method's parameter are variable args 462 ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS; 463 } 464 if (ctor.getModifiers() != ctorDescription.mModifier) { 465 resultObserver.notifyFailure( 466 FailureType.MISMATCH_METHOD, 467 ctorDescription.toReadableString(classDescription.getAbsoluteClassName()), 468 "Non-compatible method found when looking for " + 469 ctorDescription.toSignatureString()); 470 } 471 } 472 473 @Override checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffMethod methodDescription, Method method)474 protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, 475 JDiffClassDescription.JDiffMethod methodDescription, Method method) { 476 if (method.isVarArgs()) { 477 methodDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS; 478 } 479 if (method.isBridge()) { 480 methodDescription.mModifier |= METHOD_MODIFIER_BRIDGE; 481 } 482 if (method.isSynthetic()) { 483 methodDescription.mModifier |= METHOD_MODIFIER_SYNTHETIC; 484 } 485 486 // FIXME: A workaround to fix the final mismatch on enumeration 487 if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) { 488 return; 489 } 490 491 String reason; 492 if ((reason = areMethodsModifierCompatible( 493 classDescription, methodDescription, method)) != null) { 494 // Allow previous API method to be changed to abstract 495 if (isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass) 496 && (method.getModifiers() & ~(Modifier.ABSTRACT)) 497 == methodDescription.mModifier) { 498 return; 499 } 500 resultObserver.notifyFailure(FailureType.MISMATCH_METHOD, 501 methodDescription.toReadableString(classDescription.getAbsoluteClassName()), 502 String.format("Non-compatible method found when looking for %s - because %s", 503 methodDescription.toSignatureString(), reason)); 504 } 505 } 506 507 /** 508 * Checks to ensure that the modifiers value for two methods are compatible. 509 * 510 * Allowable differences are: 511 * - the native modifier is ignored 512 * 513 * @param classDescription a description of a class in an API. 514 * @param apiMethod the method read from the api file. 515 * @param reflectedMethod the method found via reflection. 516 * @return null if the method modifiers are compatible otherwise the reason why not. 517 */ areMethodsModifierCompatible( JDiffClassDescription classDescription, JDiffClassDescription.JDiffMethod apiMethod, Method reflectedMethod)518 private static String areMethodsModifierCompatible( 519 JDiffClassDescription classDescription, 520 JDiffClassDescription.JDiffMethod apiMethod, 521 Method reflectedMethod) { 522 523 // Mask off NATIVE since it is a don't care. Also mask off 524 // SYNCHRONIZED since it is not considered API significant (b/112626813) 525 int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT); 526 int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods; 527 int apiModifiers = apiMethod.mModifier & ~ignoredMods; 528 529 // A method can become non-abstract 530 if ((reflectionModifiers & Modifier.ABSTRACT) == 0) { 531 apiModifiers &= ~Modifier.ABSTRACT; 532 } 533 534 // We can ignore FINAL for classes 535 if ((classDescription.getModifier() & Modifier.FINAL) != 0) { 536 reflectionModifiers &= ~Modifier.FINAL; 537 apiModifiers &= ~Modifier.FINAL; 538 } 539 540 String genericString = reflectedMethod.toGenericString(); 541 if (IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST.contains(genericString)) { 542 reflectionModifiers &= ~Modifier.ABSTRACT; 543 apiModifiers &= ~Modifier.ABSTRACT; 544 } 545 546 if (reflectionModifiers == apiModifiers) { 547 return null; 548 } else { 549 return String.format("modifier mismatch - description (%s), method (%s), for %s", 550 getModifierString(apiModifiers), getModifierString(reflectionModifiers), genericString); 551 } 552 } 553 addBaseClass(JDiffClassDescription classDescription)554 public void addBaseClass(JDiffClassDescription classDescription) { 555 // Keep track of all the base interfaces that may by extended. 556 if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) { 557 try { 558 Class<?> runtimeClass = 559 ReflectionHelper.findMatchingClass(classDescription, classProvider); 560 interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass); 561 } catch (ClassNotFoundException e) { 562 // Do nothing. 563 } 564 } 565 } 566 } 567