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