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.JDiffConstructor; 19 import android.signature.cts.JDiffClassDescription.JDiffMethod; 20 import java.lang.annotation.Annotation; 21 import java.lang.reflect.AnnotatedElement; 22 import java.lang.reflect.Constructor; 23 import java.lang.reflect.Field; 24 import java.lang.reflect.GenericArrayType; 25 import java.lang.reflect.Member; 26 import java.lang.reflect.Method; 27 import java.lang.reflect.Modifier; 28 import java.lang.reflect.ParameterizedType; 29 import java.lang.reflect.Type; 30 import java.lang.reflect.TypeVariable; 31 import java.lang.reflect.WildcardType; 32 import java.util.ArrayList; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Set; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 40 /** 41 * Uses reflection to obtain runtime representations of elements in the API. 42 */ 43 public class ReflectionHelper { 44 45 /** 46 * Finds the reflected class for the class under test. 47 * 48 * @param classDescription the description of the class to find. 49 * @return the reflected class, or null if not found. 50 */ findMatchingClass(JDiffClassDescription classDescription, ClassProvider classProvider)51 public static Class<?> findMatchingClass(JDiffClassDescription classDescription, 52 ClassProvider classProvider) throws ClassNotFoundException { 53 // even if there are no . in the string, split will return an 54 // array of length 1 55 String shortClassName = classDescription.getShortClassName(); 56 String[] classNameParts = shortClassName.split("\\."); 57 String packageName = classDescription.getPackageName(); 58 String outermostClassName = packageName + "." + classNameParts[0]; 59 int firstInnerClassNameIndex = 0; 60 61 return searchForClass(classProvider, classDescription.getAbsoluteClassName(), 62 outermostClassName, classNameParts, 63 firstInnerClassNameIndex); 64 } 65 searchForClass( ClassProvider classProvider, String absoluteClassName, String outermostClassName, String[] classNameParts, int outerClassNameIndex)66 private static Class<?> searchForClass( 67 ClassProvider classProvider, 68 String absoluteClassName, 69 String outermostClassName, String[] classNameParts, 70 int outerClassNameIndex) throws ClassNotFoundException { 71 72 Class<?> clz = classProvider.getClass(outermostClassName); 73 if (clz.getCanonicalName().equals(absoluteClassName)) { 74 return clz; 75 } 76 77 // Then it must be an inner class. 78 for (int x = outerClassNameIndex + 1; x < classNameParts.length; x++) { 79 clz = findInnerClassByName(clz, classNameParts[x]); 80 if (clz == null) { 81 return null; 82 } 83 if (clz.getCanonicalName().equals(absoluteClassName)) { 84 return clz; 85 } 86 } 87 return null; 88 } 89 findMatchingClass(String absoluteClassName, ClassProvider classProvider)90 static Class<?> findMatchingClass(String absoluteClassName, ClassProvider classProvider) 91 throws ClassNotFoundException { 92 93 String[] classNameParts = absoluteClassName.split("\\."); 94 StringBuilder builder = new StringBuilder(); 95 String separator = ""; 96 int start; 97 for (start = 0; start < classNameParts.length; start++) { 98 String classNamePart = classNameParts[start]; 99 builder.append(separator).append(classNamePart); 100 separator = "."; 101 if (Character.isUpperCase(classNamePart.charAt(0))) { 102 break; 103 } 104 } 105 String outermostClassName = builder.toString(); 106 107 return searchForClass(classProvider, absoluteClassName, outermostClassName, classNameParts, 108 start); 109 } 110 111 /** 112 * Searches the class for the specified inner class. 113 * 114 * @param clz the class to search in. 115 * @param simpleName the simpleName of the class to find 116 * @return the class being searched for, or null if it can't be found. 117 */ findInnerClassByName(Class<?> clz, String simpleName)118 private static Class<?> findInnerClassByName(Class<?> clz, String simpleName) { 119 for (Class<?> c : clz.getDeclaredClasses()) { 120 if (c.getSimpleName().equals(simpleName)) { 121 return c; 122 } 123 } 124 return null; 125 } 126 127 /** 128 * Searches available constructor. 129 * 130 * @param runtimeClass the class in which to search. 131 * @param jdiffDes constructor description to find. 132 * @param mismatchReasons a map from rejected constructor to the reason it was rejected. 133 * @return reflected constructor, or null if not found. 134 */ findMatchingConstructor(Class<?> runtimeClass, JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons)135 static Constructor<?> findMatchingConstructor(Class<?> runtimeClass, 136 JDiffConstructor jdiffDes, Map<Constructor, String> mismatchReasons) { 137 138 for (Constructor<?> c : runtimeClass.getDeclaredConstructors()) { 139 Type[] params = c.getGenericParameterTypes(); 140 boolean isStaticClass = ((runtimeClass.getModifiers() & Modifier.STATIC) != 0); 141 142 int startParamOffset = 0; 143 int numberOfParams = params.length; 144 145 // non-static inner class -> skip implicit parent pointer 146 // as first arg 147 if (runtimeClass.isMemberClass() && !isStaticClass && params.length >= 1) { 148 startParamOffset = 1; 149 --numberOfParams; 150 } 151 152 ArrayList<String> jdiffParamList = jdiffDes.mParamList; 153 if (jdiffParamList.size() == numberOfParams) { 154 boolean isFound = true; 155 // i counts jdiff params, j counts reflected params 156 int i = 0; 157 int j = startParamOffset; 158 while (i < jdiffParamList.size()) { 159 String expectedParameter = jdiffParamList.get(i); 160 Type actualParameter = params[j]; 161 if (!compareParam(expectedParameter, actualParameter, 162 DefaultTypeComparator.INSTANCE)) { 163 mismatchReasons.put(c, 164 String.format("parameter %d mismatch: expected (%s), found (%s)", 165 i, 166 expectedParameter, 167 actualParameter)); 168 isFound = false; 169 break; 170 } 171 ++i; 172 ++j; 173 } 174 if (isFound) { 175 return c; 176 } 177 } else { 178 mismatchReasons.put(c, 179 String.format("parameter list length mismatch: expected %d, found %d", 180 jdiffParamList.size(), 181 params.length)); 182 } 183 } 184 return null; 185 } 186 187 /** 188 * Compares the parameter from the API and the parameter from 189 * reflection. 190 * 191 * @param jdiffParam param parsed from the API xml file. 192 * @param reflectionParamType param gotten from the Java reflection. 193 * @param typeComparator compares two types to determine if they are equal. 194 * @return True if the two params match, otherwise return false. 195 */ compareParam(String jdiffParam, Type reflectionParamType, TypeComparator typeComparator)196 private static boolean compareParam(String jdiffParam, Type reflectionParamType, 197 TypeComparator typeComparator) { 198 if (jdiffParam == null) { 199 return false; 200 } 201 202 String reflectionParam = typeToString(reflectionParamType); 203 // Most things aren't varargs, so just do a simple compare 204 // first. 205 if (typeComparator.compare(jdiffParam, reflectionParam)) { 206 return true; 207 } 208 209 // Check for varargs. jdiff reports varargs as ..., while 210 // reflection reports them as [] 211 int jdiffParamEndOffset = jdiffParam.indexOf("..."); 212 int reflectionParamEndOffset = reflectionParam != null ? reflectionParam.indexOf("[]") : -1; 213 if (jdiffParamEndOffset != -1 && reflectionParamEndOffset != -1) { 214 jdiffParam = jdiffParam.substring(0, jdiffParamEndOffset); 215 reflectionParam = reflectionParam.substring(0, reflectionParamEndOffset); 216 return typeComparator.compare(jdiffParam, reflectionParam); 217 } 218 219 return false; 220 } 221 222 /** 223 * Finds the reflected method specified by the method description. 224 * 225 * @param runtimeClass the class in which to search. 226 * @param method description of the method to find 227 * @param mismatchReasons a map from rejected method to the reason it was rejected, only 228 * contains methods with the same name. 229 * @return the reflected method, or null if not found. 230 */ findMatchingMethod( Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons)231 static Method findMatchingMethod( 232 Class<?> runtimeClass, JDiffMethod method, Map<Method, String> mismatchReasons) { 233 234 // Search through the class to find the methods just in case the method was actually 235 // declared in a superclass which is not part of the API and so was made to appear as if 236 // it was declared in each of the hidden class' subclasses. Cannot use getMethods() as that 237 // will only return public methods and the API includes protected methods. 238 Class<?> currentClass = runtimeClass; 239 while (currentClass != null) { 240 Method[] reflectedMethods = currentClass.getDeclaredMethods(); 241 242 for (Method reflectedMethod : reflectedMethods) { 243 // If the method names aren't equal, the methods can't match. 244 if (!method.mName.equals(reflectedMethod.getName())) { 245 continue; 246 } 247 248 if (matchesSignature(method, reflectedMethod, mismatchReasons)) { 249 return reflectedMethod; 250 } 251 } 252 253 currentClass = currentClass.getSuperclass(); 254 } 255 256 return null; 257 } 258 259 /** 260 * Checks if the two types of methods are the same. 261 * 262 * @param jDiffMethod the jDiffMethod to compare 263 * @param reflectedMethod the reflected method to compare 264 * @param mismatchReasons map from method to reason it did not match, used when reporting 265 * missing methods. 266 * @return true, if both methods are the same 267 */ matchesSignature(JDiffMethod jDiffMethod, Method reflectedMethod, Map<Method, String> mismatchReasons)268 static boolean matchesSignature(JDiffMethod jDiffMethod, Method reflectedMethod, 269 Map<Method, String> mismatchReasons) { 270 // If the method is a bridge then use a special comparator for comparing types as 271 // bridge methods created for generic methods may not have generic signatures. 272 // See b/123558763 for more information. 273 TypeComparator typeComparator = reflectedMethod.isBridge() 274 ? BridgeTypeComparator.INSTANCE : DefaultTypeComparator.INSTANCE; 275 276 String jdiffReturnType = jDiffMethod.mReturnType; 277 String reflectionReturnType = typeToString(reflectedMethod.getGenericReturnType()); 278 279 // Next, compare the return types of the two methods. If 280 // they aren't equal, the methods can't match. 281 if (!typeComparator.compare(jdiffReturnType, reflectionReturnType)) { 282 mismatchReasons.put(reflectedMethod, 283 String.format("return type mismatch: expected %s, found %s", jdiffReturnType, 284 reflectionReturnType)); 285 return false; 286 } 287 288 List<String> jdiffParamList = jDiffMethod.mParamList; 289 Type[] params = reflectedMethod.getGenericParameterTypes(); 290 291 // Next, check the method parameters. If they have different 292 // parameter lengths, the two methods can't match. 293 if (jdiffParamList.size() != params.length) { 294 mismatchReasons.put(reflectedMethod, 295 String.format("parameter list length mismatch: expected %s, found %s", 296 jdiffParamList.size(), 297 params.length)); 298 return false; 299 } 300 301 boolean piecewiseParamsMatch = true; 302 303 // Compare method parameters piecewise and return true if they all match. 304 for (int i = 0; i < jdiffParamList.size(); i++) { 305 piecewiseParamsMatch &= compareParam(jdiffParamList.get(i), params[i], typeComparator); 306 } 307 if (piecewiseParamsMatch) { 308 return true; 309 } 310 311 /* NOTE: There are cases where piecewise method parameter checking 312 * fails even though the strings are equal, so compare entire strings 313 * against each other. This is not done by default to avoid a 314 * TransactionTooLargeException. 315 * Additionally, this can fail anyway due to extra 316 * information dug up by reflection. 317 * 318 * TODO: fix parameter equality checking and reflection matching 319 * See https://b.corp.google.com/issues/27726349 320 */ 321 322 StringBuilder reflectedMethodParams = new StringBuilder(""); 323 StringBuilder jdiffMethodParams = new StringBuilder(""); 324 325 String sep = ""; 326 for (int i = 0; i < jdiffParamList.size(); i++) { 327 jdiffMethodParams.append(sep).append(jdiffParamList.get(i)); 328 reflectedMethodParams.append(sep).append(params[i].getTypeName()); 329 sep = ", "; 330 } 331 332 String jDiffFName = jdiffMethodParams.toString(); 333 String refName = reflectedMethodParams.toString(); 334 335 boolean signatureMatches = jDiffFName.equals(refName); 336 if (!signatureMatches) { 337 mismatchReasons.put(reflectedMethod, 338 String.format("parameter signature mismatch: expected (%s), found (%s)", 339 jDiffFName, 340 refName)); 341 } 342 343 return signatureMatches; 344 } 345 346 /** 347 * Converts WildcardType array into a jdiff compatible string.. 348 * This is a helper function for typeToString. 349 * 350 * @param types array of types to format. 351 * @return the jdiff formatted string. 352 */ concatWildcardTypes(Type[] types)353 private static String concatWildcardTypes(Type[] types) { 354 StringBuilder sb = new StringBuilder(); 355 int elementNum = 0; 356 for (Type t : types) { 357 sb.append(typeToString(t)); 358 if (++elementNum < types.length) { 359 sb.append(" & "); 360 } 361 } 362 return sb.toString(); 363 } 364 365 /** 366 * Converts a Type into a jdiff compatible String. The returned 367 * types from this function should match the same Strings that 368 * jdiff is providing to us. 369 * 370 * @param type the type to convert. 371 * @return the jdiff formatted string. 372 */ typeToString(Type type)373 public static String typeToString(Type type) { 374 if (type instanceof ParameterizedType) { 375 ParameterizedType pt = (ParameterizedType) type; 376 377 StringBuilder sb = new StringBuilder(); 378 sb.append(typeToString(pt.getRawType())); 379 sb.append("<"); 380 381 int elementNum = 0; 382 Type[] types = pt.getActualTypeArguments(); 383 for (Type t : types) { 384 sb.append(typeToString(t)); 385 if (++elementNum < types.length) { 386 // Must match separator used in 387 // android.signature.cts.KtHelper.toDefaultTypeString. 388 sb.append(","); 389 } 390 } 391 392 sb.append(">"); 393 return sb.toString(); 394 } else if (type instanceof TypeVariable) { 395 return ((TypeVariable<?>) type).getName(); 396 } else if (type instanceof Class) { 397 return ((Class<?>) type).getCanonicalName(); 398 } else if (type instanceof GenericArrayType) { 399 String typeName = typeToString(((GenericArrayType) type).getGenericComponentType()); 400 return typeName + "[]"; 401 } else if (type instanceof WildcardType) { 402 WildcardType wt = (WildcardType) type; 403 Type[] lowerBounds = wt.getLowerBounds(); 404 if (lowerBounds.length == 0) { 405 String name = "? extends " + concatWildcardTypes(wt.getUpperBounds()); 406 407 // Special case for ? 408 if (name.equals("? extends java.lang.Object")) { 409 return "?"; 410 } else { 411 return name; 412 } 413 } else { 414 String name = concatWildcardTypes(wt.getUpperBounds()) + 415 " super " + 416 concatWildcardTypes(wt.getLowerBounds()); 417 // Another special case for ? 418 name = name.replace("java.lang.Object", "?"); 419 return name; 420 } 421 } else { 422 throw new RuntimeException("Got an unknown java.lang.Type"); 423 } 424 } 425 426 private final static Pattern REPEATING_ANNOTATION_PATTERN = 427 Pattern.compile("@.*\\(value=\\[(.*)\\]\\)"); 428 hasMatchingAnnotation(AnnotatedElement elem, String annotationSpec)429 public static boolean hasMatchingAnnotation(AnnotatedElement elem, String annotationSpec) { 430 for (Annotation a : elem.getAnnotations()) { 431 if (a.toString().equals(annotationSpec)) { 432 return true; 433 } 434 // It could be a repeating annotation. In that case, a.toString() returns 435 // "@MyAnnotation$Container(value=[@MyAnnotation(A), @MyAnnotation(B)])" 436 // Then, iterate over @MyAnnotation(A) and @MyAnnotation(B). 437 Matcher m = REPEATING_ANNOTATION_PATTERN.matcher(a.toString()); 438 if (m.matches()) { 439 for (String token : m.group(1).split(", ")) { 440 if (token.equals(annotationSpec)) { 441 return true; 442 } 443 } 444 } 445 } 446 return false; 447 } 448 449 /** 450 * Returns a list of constructors which are annotated with the given annotation class. 451 */ getAnnotatedConstructors(Class<?> clazz, String annotationSpec)452 public static Set<Constructor<?>> getAnnotatedConstructors(Class<?> clazz, 453 String annotationSpec) { 454 Set<Constructor<?>> result = new HashSet<>(); 455 if (annotationSpec != null) { 456 for (Constructor<?> c : clazz.getDeclaredConstructors()) { 457 if (hasMatchingAnnotation(c, annotationSpec)) { 458 // TODO(b/71630695): currently, some API members are not annotated, because 459 // a member is automatically added to the API set if it is in a class with 460 // annotation and it is not @hide. <member>.getDeclaringClass(). 461 // isAnnotationPresent(annotationClass) won't help because it will then 462 // incorrectly include non-API members which are marked as @hide; 463 // @hide isn't visible at runtime. Until the issue is fixed, we should 464 // omit those automatically added API members from the test. 465 result.add(c); 466 } 467 } 468 } 469 return result; 470 } 471 472 /** 473 * Returns a list of methods which are annotated with the given annotation class. 474 */ getAnnotatedMethods(Class<?> clazz, String annotationSpec)475 public static Set<Method> getAnnotatedMethods(Class<?> clazz, String annotationSpec) { 476 Set<Method> result = new HashSet<>(); 477 if (annotationSpec != null) { 478 for (Method m : clazz.getDeclaredMethods()) { 479 if (hasMatchingAnnotation(m, annotationSpec)) { 480 // TODO(b/71630695): see getAnnotatedConstructors for details 481 result.add(m); 482 } 483 } 484 } 485 return result; 486 } 487 488 /** 489 * Returns a list of fields which are annotated with the given annotation class. 490 */ getAnnotatedFields(Class<?> clazz, String annotationSpec)491 public static Set<Field> getAnnotatedFields(Class<?> clazz, String annotationSpec) { 492 Set<Field> result = new HashSet<>(); 493 if (annotationSpec != null) { 494 for (Field f : clazz.getDeclaredFields()) { 495 if (hasMatchingAnnotation(f, annotationSpec)) { 496 // TODO(b/71630695): see getAnnotatedConstructors for details 497 result.add(f); 498 } 499 } 500 } 501 return result; 502 } 503 isInAnnotatedClass(Member m, String annotationSpec)504 private static boolean isInAnnotatedClass(Member m, String annotationSpec) { 505 Class<?> clazz = m.getDeclaringClass(); 506 do { 507 if (hasMatchingAnnotation(clazz, annotationSpec)) { 508 return true; 509 } 510 } while ((clazz = clazz.getDeclaringClass()) != null); 511 return false; 512 } 513 isAnnotatedOrInAnnotatedClass(Field field, String annotationSpec)514 public static boolean isAnnotatedOrInAnnotatedClass(Field field, String annotationSpec) { 515 if (annotationSpec == null) { 516 return true; 517 } 518 return hasMatchingAnnotation(field, annotationSpec) 519 || isInAnnotatedClass(field, annotationSpec); 520 } 521 isAnnotatedOrInAnnotatedClass(Constructor<?> constructor, String annotationSpec)522 public static boolean isAnnotatedOrInAnnotatedClass(Constructor<?> constructor, 523 String annotationSpec) { 524 if (annotationSpec == null) { 525 return true; 526 } 527 return hasMatchingAnnotation(constructor, annotationSpec) 528 || isInAnnotatedClass(constructor, annotationSpec); 529 } 530 isAnnotatedOrInAnnotatedClass(Method method, String annotationSpec)531 public static boolean isAnnotatedOrInAnnotatedClass(Method method, String annotationSpec) { 532 if (annotationSpec == null) { 533 return true; 534 } 535 return hasMatchingAnnotation(method, annotationSpec) 536 || isInAnnotatedClass(method, annotationSpec); 537 } 538 isOverridingAnnotatedMethod(Method method, String annotationSpec)539 public static boolean isOverridingAnnotatedMethod(Method method, String annotationSpec) { 540 Class<?> clazz = method.getDeclaringClass(); 541 while (!(clazz = clazz.getSuperclass()).equals(Object.class)) { 542 try { 543 Method overriddenMethod; 544 overriddenMethod = clazz.getDeclaredMethod(method.getName(), 545 method.getParameterTypes()); 546 if (overriddenMethod != null) { 547 return isAnnotatedOrInAnnotatedClass(overriddenMethod, annotationSpec); 548 } 549 } catch (NoSuchMethodException e) { 550 continue; 551 } catch (SecurityException e) { 552 throw new RuntimeException( 553 "Error while searching for overridden method. " + method.toString(), e); 554 } 555 } 556 return false; 557 } 558 findRequiredClass(JDiffClassDescription classDescription, ClassProvider classProvider)559 static Class<?> findRequiredClass(JDiffClassDescription classDescription, 560 ClassProvider classProvider) { 561 try { 562 return findMatchingClass(classDescription, classProvider); 563 } catch (ClassNotFoundException e) { 564 LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e); 565 return null; 566 } 567 } 568 569 /** 570 * Compare the string representation of types for equality. 571 */ 572 interface TypeComparator { compare(String apiType, String reflectedType)573 boolean compare(String apiType, String reflectedType); 574 } 575 576 /** 577 * Compare the types using their default signature, i.e. generic for generic methods, otherwise 578 * basic types. 579 */ 580 static class DefaultTypeComparator implements TypeComparator { 581 static final TypeComparator INSTANCE = new DefaultTypeComparator(); 582 @Override compare(String apiType, String reflectedType)583 public boolean compare(String apiType, String reflectedType) { 584 return apiType.equals(reflectedType); 585 } 586 } 587 588 /** 589 * Comparator for the types of bridge methods. 590 * 591 * <p>Bridge methods may not have generic signatures so compare as for 592 * {@link DefaultTypeComparator}, but if they do not match and the api type is 593 * generic then fall back to comparing their raw types. 594 */ 595 static class BridgeTypeComparator implements TypeComparator { 596 static final TypeComparator INSTANCE = new BridgeTypeComparator(); 597 @Override compare(String apiType, String reflectedType)598 public boolean compare(String apiType, String reflectedType) { 599 if (DefaultTypeComparator.INSTANCE.compare(apiType, reflectedType)) { 600 return true; 601 } 602 603 // If the method is a bridge method and the return types are generic then compare the 604 // non generic types as bridge methods do not have generic types. 605 int index = apiType.indexOf('<'); 606 if (index != -1) { 607 String rawReturnType = apiType.substring(0, index); 608 return rawReturnType.equals(reflectedType); 609 } 610 return false; 611 } 612 } 613 } 614