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