1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.signature.cts;
17 
18 import android.signature.cts.JDiffClassDescription.JDiffField;
19 import android.signature.cts.ReflectionHelper.DefaultTypeComparator;
20 
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Modifier;
25 import java.util.Formatter;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
31 
32 /**
33  * Checks that the runtime representation of a class matches the API representation of a class.
34  */
35 public class ApiComplianceChecker extends ApiPresenceChecker {
36 
37     /**
38      * A set of field values signatures whose value modifier should be ignored.
39      *
40      * <p>If a field value is intended to be changed to correct its value, that change should be
41      * allowed. The field name is the key of the ignoring map, and a FieldValuePair which is a pair
42      * of the old value and the new value is the value of the ignoring map.
43      * WARNING: Entries should only be added after consulting API council.
44      */
45     private static class FieldValuePair {
46         private String oldValue;
47         private String newValue;
48 
FieldValuePair(String oldValue, String newValue)49         private FieldValuePair(String oldValue, String newValue) {
50             this.oldValue = oldValue;
51             this.newValue = newValue;
52         }
53     };
54     private static final Map<String, FieldValuePair> IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST =
55             new HashMap<String, FieldValuePair>();
56     static {
57         // This field value was previously wrong. As the CtsSystemApiSignatureTestCases package
58         // tests both the old and new specifications with both old and new values, this needs to be
59         // ignored.
60         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
61                 "android.media.tv.tuner.frontend.FrontendSettings#FEC_28_45(long)",
62                 new FieldValuePair("-2147483648", "2147483648"));
63         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
64                 "android.media.tv.tuner.frontend.FrontendSettings#FEC_29_45(long)",
65                 new FieldValuePair("1", "4294967296"));
66         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
67                 "android.media.tv.tuner.frontend.FrontendSettings#FEC_31_45(long)",
68                 new FieldValuePair("2", "8589934592"));
69         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
70                 "android.media.tv.tuner.frontend.FrontendSettings#FEC_32_45(long)",
71                 new FieldValuePair("4", "17179869184"));
72         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
73                 "android.media.tv.tuner.frontend.FrontendSettings#FEC_77_90(long)",
74                 new FieldValuePair("8", "34359738368"));
75         // Allow for change in toString() conversion for Float.MIN_NORMAL (b/328666063).
76         IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
77                 "java.lang.Float#MIN_NORMAL(float)",
78                 new FieldValuePair("1.1754944E-38", "1.17549435E-38"));
79     }
80 
81     /** Indicates that the class is an annotation. */
82     private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000;
83 
84     /** Indicates that the class is an enum. */
85     private static final int CLASS_MODIFIER_ENUM       = 0x00004000;
86 
87     /** Indicates that the method is a bridge method. */
88     private static final int METHOD_MODIFIER_BRIDGE    = 0x00000040;
89 
90     /** Indicates that the method is takes a variable number of arguments. */
91     private static final int METHOD_MODIFIER_VAR_ARGS  = 0x00000080;
92 
93     /** Indicates that the method is a synthetic method. */
94     private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000;
95 
96     /** Indicates that a field is an enum value. */
97     public static final int FIELD_MODIFIER_ENUM_VALUE = 0x00004000;
98 
99     private final InterfaceChecker interfaceChecker;
100 
ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider)101     public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
102         super(classProvider, resultObserver);
103         interfaceChecker = new InterfaceChecker(resultObserver, classProvider);
104     }
105 
checkDeferred()106     public void checkDeferred() {
107         interfaceChecker.checkQueued();
108     }
109 
110     @Override
checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass)111     protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) {
112         if (JDiffClassDescription.JDiffType.INTERFACE.equals(classDescription.getClassType())) {
113             // Queue the interface for deferred checking.
114             interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass);
115         }
116 
117         String reason;
118         if ((reason = checkClassModifiersCompliance(classDescription, runtimeClass)) != null) {
119             resultObserver.notifyFailure(FailureType.mismatch(classDescription),
120                     classDescription.getAbsoluteClassName(),
121                     String.format("Non-compatible class found when looking for %s - because %s",
122                             classDescription.toSignatureString(), reason));
123             return false;
124         }
125 
126         if (!checkClassAnnotationCompliance(classDescription, runtimeClass)) {
127             resultObserver.notifyFailure(FailureType.mismatch(classDescription),
128                     classDescription.getAbsoluteClassName(), "Annotation mismatch");
129             return false;
130         }
131 
132         if (!runtimeClass.isAnnotation()) {
133             // check father class
134             if (!checkClassExtendsCompliance(classDescription, runtimeClass)) {
135                 resultObserver.notifyFailure(FailureType.mismatch(classDescription),
136                         classDescription.getAbsoluteClassName(),
137                         "Extends mismatch, expected " + classDescription.getExtendedClass());
138                 return false;
139             }
140 
141             // check implements interface
142             if (!checkClassImplementsCompliance(classDescription, runtimeClass)) {
143                 resultObserver.notifyFailure(FailureType.mismatch(classDescription),
144                         classDescription.getAbsoluteClassName(),
145                         "Implements mismatch, expected " + classDescription.getImplInterfaces());
146                 return false;
147             }
148         }
149         return true;
150     }
151 
152     /**
153      * Check if the class definition is from a previous API and was neither instantiable nor
154      * extensible through that API.
155      *
156      * <p>Such a class is more flexible in how it can be modified than other classes as there is
157      * no way to either create or extend the class.</p>
158      *
159      * <p>A class that has no constructors in the API cannot be instantiated or extended. Such a
160      * class has a lot more flexibility when it comes to making forwards compatible changes than
161      * other classes. e.g. Normally, a non-final class cannot be made final as that would break any
162      * code that extended the class but if there are no constructors in the API then it is
163      * impossible to extend it through the API so making it final is forwards compatible.</p>
164      *
165      * <p>Similarly, a concrete class cannot normally be made abstract as that would break any code
166      * that attempted to instantiate it but if there are no constructors in the API then it is
167      * impossible to instantiate it so making it abstract is forwards compatible.</p>
168      *
169      * <p>Finally, a non-static class cannot normally be made static (or vice versa) as that would
170      * break any code that attemped to instantiate it but if there are no constructors in the API
171      * then it is impossible to instantiate so changing the static flag is forwards compatible.</p>
172      *
173      * <p>In a similar fashion the abstract and final (but not static) modifier can be added to a
174      * method on this type of class.</p>
175      *
176      * <p>In this case forwards compatible is restricted to compile time and runtime behavior. It
177      * does not cover testing. e.g. making a class that was previously non-final could break tests
178      * that relied on mocking that class. However, that is a non-standard use of the API and so we
179      * are not strictly required to maintain compatibility in that case. It should also only be a
180      * minor issue as most mocking libraries support mocking final classes now.</p>
181      *
182      * @param classDescription a description of a class in an API.
183      */
classIsNotInstantiableOrExtensibleInPreviousApi( JDiffClassDescription classDescription)184     private static boolean classIsNotInstantiableOrExtensibleInPreviousApi(
185             JDiffClassDescription classDescription) {
186         return classDescription.getConstructors().isEmpty()
187                 && classDescription.isPreviousApi();
188     }
189 
190     /**
191      * If a modifier (final or abstract) has been removed since the previous API was published then
192      * it is forwards compatible so clear the modifier flag in the previous API modifiers so that it
193      * does not cause a mismatch.
194      *
195      * @param previousModifiers The set of modifiers for the previous API.
196      * @param currentModifiers The set of modifiers for the current implementation class.
197      * @return the normalized previous modifiers.
198      */
normalizePreviousModifiersIfModifierIsRemoved( int previousModifiers, int currentModifiers, int... flags)199     private static int normalizePreviousModifiersIfModifierIsRemoved(
200             int previousModifiers, int currentModifiers, int... flags) {
201         for (int flag : flags) {
202             // If the flag was present in the previous API but is no longer present then the
203             // modifier has been removed.
204             if ((previousModifiers & flag) != 0 && (currentModifiers & flag) == 0) {
205                 previousModifiers &= ~flag;
206             }
207         }
208 
209         return previousModifiers;
210     }
211 
212     /**
213      * If a modifier (final or abstract) has been added since the previous API was published then
214      * this treats it as forwards compatible and clears the modifier flag in the current API
215      * modifiers so that it does not cause a mismatch.
216      *
217      * <p>This must only be called when adding one of the supplied modifiers is forwards compatible,
218      * e.g. when called on a class or methods from a class that returns true for
219      * {@link #classIsNotInstantiableOrExtensibleInPreviousApi(JDiffClassDescription)}.</p>
220      *
221      * @param previousModifiers The set of modifiers for the previous API.
222      * @param currentModifiers The set of modifiers for the current implementation class.
223      * @return the normalized current modifiers.
224      */
normalizeCurrentModifiersIfModifierIsAdded( int previousModifiers, int currentModifiers, int... flags)225     private static int normalizeCurrentModifiersIfModifierIsAdded(
226             int previousModifiers, int currentModifiers, int... flags) {
227         for (int flag : flags) {
228             // If the flag was not present in the previous API but is present then the modifier has
229             // been added.
230             if ((previousModifiers & flag) == 0 && (currentModifiers & flag) != 0) {
231                 currentModifiers &= ~flag;
232             }
233         }
234 
235         return currentModifiers;
236     }
237 
238     /**
239      * Checks if the class under test has compliant modifiers compared to the API.
240      *
241      * @param classDescription a description of a class in an API.
242      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
243      * @return null if modifiers are compliant otherwise a reason why they are not.
244      */
checkClassModifiersCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)245     private static String checkClassModifiersCompliance(JDiffClassDescription classDescription,
246             Class<?> runtimeClass) {
247         int reflectionModifiers = runtimeClass.getModifiers();
248         int apiModifiers = classDescription.getModifier();
249 
250         // If the api class is an interface then always treat it as abstract.
251         // interfaces are implicitly abstract (JLS 9.1.1.1)
252         if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) {
253             apiModifiers |= Modifier.ABSTRACT;
254         }
255 
256         if (classDescription.isAnnotation()) {
257             reflectionModifiers &= ~CLASS_MODIFIER_ANNOTATION;
258         }
259         if (runtimeClass.isInterface()) {
260             reflectionModifiers &= ~(Modifier.INTERFACE);
261         }
262         if (classDescription.isEnumType() && runtimeClass.isEnum()) {
263             reflectionModifiers &= ~CLASS_MODIFIER_ENUM;
264 
265             // Most enums are marked as final, however enums that have one or more constants that
266             // override a method from the class cannot be marked as final because those constants
267             // are represented as a subclass. As enum classes cannot be extended (except for its own
268             // constants) there is no benefit in checking final modifier so just ignore them.
269             //
270             // Ditto for abstract.
271             reflectionModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT);
272             apiModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT);
273         }
274 
275         if (classDescription.isPreviousApi()) {
276             // If the final and/or abstract modifiers have been removed since the previous API was
277             // published then that is forwards compatible so remove the modifier in the previous API
278             // modifiers so they match the runtime modifiers.
279             apiModifiers = normalizePreviousModifiersIfModifierIsRemoved(
280                     apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
281 
282             if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) {
283                 // Adding the final, abstract or static flags to the runtime class is forwards
284                 // compatible as the class cannot be instantiated or extended. Clear the flags for
285                 // any such added modifier from the current implementation's modifiers so that it
286                 // does not cause a mismatch.
287                 reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded(
288                         apiModifiers, reflectionModifiers,
289                         Modifier.FINAL, Modifier.ABSTRACT, Modifier.STATIC);
290             }
291         }
292 
293         if ((reflectionModifiers == apiModifiers)
294                 && (classDescription.isEnumType() == runtimeClass.isEnum())) {
295             return null;
296         } else {
297             return String.format("modifier mismatch - description (%s), class (%s)",
298                     getModifierString(apiModifiers), getModifierString(reflectionModifiers));
299         }
300     }
301 
302     /**
303      * Checks if the class under test is compliant with regards to
304      * annnotations when compared to the API.
305      *
306      * @param classDescription a description of a class in an API.
307      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
308      * @return true if the class is compliant
309      */
checkClassAnnotationCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)310     private static boolean checkClassAnnotationCompliance(JDiffClassDescription classDescription,
311             Class<?> runtimeClass) {
312         if (runtimeClass.isAnnotation()) {
313             // check annotation
314             for (String inter : classDescription.getImplInterfaces()) {
315                 if ("java.lang.annotation.Annotation".equals(inter)) {
316                     return true;
317                 }
318             }
319             return false;
320         }
321         return true;
322     }
323 
324     /**
325      * Checks if the class under test extends the proper classes
326      * according to the API.
327      *
328      * @param classDescription a description of a class in an API.
329      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
330      * @return true if the class is compliant.
331      */
checkClassExtendsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)332     private static boolean checkClassExtendsCompliance(JDiffClassDescription classDescription,
333             Class<?> runtimeClass) {
334         // Nothing to check if it doesn't extend anything.
335         if (classDescription.getExtendedClass() != null) {
336             Class<?> superClass = runtimeClass.getSuperclass();
337 
338             while (superClass != null) {
339                 if (superClass.getCanonicalName().equals(classDescription.getExtendedClass())) {
340                     return true;
341                 }
342                 superClass = superClass.getSuperclass();
343             }
344             // Couldn't find a matching superclass.
345             return false;
346         }
347         return true;
348     }
349 
350     /**
351      * Checks if the class under test implements the proper interfaces
352      * according to the API.
353      *
354      * @param classDescription a description of a class in an API.
355      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
356      * @return true if the class is compliant
357      */
checkClassImplementsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)358     private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription,
359             Class<?> runtimeClass) {
360         Set<String> interFaceSet = new HashSet<>();
361 
362         addInterfacesToSetByName(runtimeClass, interFaceSet);
363 
364         for (String inter : classDescription.getImplInterfaces()) {
365             if (!interFaceSet.contains(inter)) {
366                 return false;
367             }
368         }
369         return true;
370     }
371 
addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet)372     private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) {
373         Class<?>[] interfaces = runtimeClass.getInterfaces();
374         for (Class<?> c : interfaces) {
375             interFaceSet.add(c.getCanonicalName());
376             // Add grandparent interfaces in case the parent interface is hidden.
377             addInterfacesToSetByName(c, interFaceSet);
378         }
379 
380         // Add the interfaces that the super class implements as well just in case the super class
381         // is hidden.
382         Class<?> superClass = runtimeClass.getSuperclass();
383         if (superClass != null) {
384             addInterfacesToSetByName(superClass, interFaceSet);
385         }
386     }
387 
388     @Override
checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffField fieldDescription, Field field)389     protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass,
390             JDiffField fieldDescription, Field field) {
391         int expectedModifiers = fieldDescription.mModifier;
392         int actualModifiers = field.getModifiers();
393         if (actualModifiers != expectedModifiers) {
394             resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
395                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
396                     String.format(
397                             "Incompatible field modifiers, expected %s, found %s",
398                             getModifierString(expectedModifiers),
399                             getModifierString(actualModifiers)));
400         }
401 
402         String expectedFieldType = fieldDescription.mFieldType;
403         String actualFieldType = ReflectionHelper.typeToString(field.getGenericType());
404         if (!DefaultTypeComparator.INSTANCE.compare(expectedFieldType, actualFieldType)) {
405             resultObserver.notifyFailure(
406                     FailureType.MISMATCH_FIELD,
407                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
408                     String.format("Incompatible field type found, expected %s, found %s",
409                             expectedFieldType, actualFieldType));
410         }
411 
412         String message = checkFieldValueCompliance(classDescription, fieldDescription, field);
413         if (message != null) {
414             resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
415                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
416                     message);
417         }
418     }
419 
420     private static final int BRIDGE    = 0x00000040;
421     private static final int VARARGS   = 0x00000080;
422     private static final int SYNTHETIC = 0x00001000;
423     private static final int ANNOTATION  = 0x00002000;
424     private static final int ENUM      = 0x00004000;
425     private static final int MANDATED  = 0x00008000;
426 
getModifierString(int modifiers)427     private static String getModifierString(int modifiers) {
428         Formatter formatter = new Formatter();
429         String m = Modifier.toString(modifiers);
430         formatter.format("<%s", m);
431         String sep = m.isEmpty() ? "" : " ";
432         if ((modifiers & BRIDGE) != 0) {
433             formatter.format("%senum", sep);
434             sep = " ";
435         }
436         if ((modifiers & VARARGS) != 0) {
437             formatter.format("%svarargs", sep);
438             sep = " ";
439         }
440         if ((modifiers & SYNTHETIC) != 0) {
441             formatter.format("%ssynthetic", sep);
442             sep = " ";
443         }
444         if ((modifiers & ANNOTATION) != 0) {
445             formatter.format("%sannotation", sep);
446             sep = " ";
447         }
448         if ((modifiers & ENUM) != 0) {
449             formatter.format("%senum", sep);
450             sep = " ";
451         }
452         if ((modifiers & MANDATED) != 0) {
453             formatter.format("%smandated", sep);
454         }
455         return formatter.format("> (0x%x)", modifiers).toString();
456     }
457 
458     /**
459      * Checks whether the field values are compatible.
460      *
461      * @param apiField The field as defined by the platform API.
462      * @param deviceField The field as defined by the device under test.
463      */
checkFieldValueCompliance( JDiffClassDescription classDescription, JDiffField apiField, Field deviceField)464     private static String checkFieldValueCompliance(
465             JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) {
466         if ((apiField.mModifier & Modifier.FINAL) == 0 ||
467                 (apiField.mModifier & Modifier.STATIC) == 0) {
468             // Only final static fields can have fixed values.
469             return null;
470         }
471         String apiFieldValue = apiField.getValueString();
472         if (apiFieldValue == null) {
473             // If we don't define a constant value for it, then it can be anything.
474             return null;
475         }
476 
477         // Convert char into a number to match the value returned from device field. The device
478         // field does not
479         if (deviceField.getType() == char.class) {
480             apiFieldValue = convertCharToCanonicalValue(apiFieldValue.charAt(0));
481         }
482 
483         String deviceFieldValue = getFieldValueAsString(deviceField);
484         if (!Objects.equals(apiFieldValue, deviceFieldValue)) {
485             String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName());
486             if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName)
487                     && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals(
488                             apiFieldValue)
489                     && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals(
490                             deviceFieldValue)) {
491                 return null;
492             }
493             return String.format("Incorrect field value, expected <%s>, found <%s>",
494                     apiFieldValue, deviceFieldValue);
495 
496         }
497 
498         return null;
499     }
500 
getFieldValueAsString(Field deviceField)501     private static String getFieldValueAsString(Field deviceField) {
502         // Some fields may be protected or package-private
503         deviceField.setAccessible(true);
504         try {
505             Class<?> fieldType = deviceField.getType();
506             if (fieldType == byte.class) {
507                 return Byte.toString(deviceField.getByte(null));
508             } else if (fieldType == char.class) {
509                 return convertCharToCanonicalValue(deviceField.getChar(null));
510             } else if (fieldType == short.class) {
511                 return  Short.toString(deviceField.getShort(null));
512             } else if (fieldType == int.class) {
513                 return  Integer.toString(deviceField.getInt(null));
514             } else if (fieldType == long.class) {
515                 return Long.toString(deviceField.getLong(null));
516             } else if (fieldType == float.class) {
517                 return  canonicalizeFloatingPoint(
518                                 Float.toString(deviceField.getFloat(null)));
519             } else if (fieldType == double.class) {
520                 return  canonicalizeFloatingPoint(
521                                 Double.toString(deviceField.getDouble(null)));
522             } else if (fieldType == boolean.class) {
523                 return  Boolean.toString(deviceField.getBoolean(null));
524             } else if (fieldType == java.lang.String.class) {
525                 return (String) deviceField.get(null);
526             } else {
527                 return null;
528             }
529         } catch (IllegalAccessException e) {
530             throw new RuntimeException(e);
531         }
532     }
533 
convertCharToCanonicalValue(char c)534     private static String convertCharToCanonicalValue(char c) {
535         return String.format("'%c' (0x%x)", c, (int) c);
536     }
537 
538     /**
539      * Canonicalize the string representation of floating point numbers.
540      *
541      * This needs to be kept in sync with the doclava canonicalization.
542      */
canonicalizeFloatingPoint(String val)543     private static String canonicalizeFloatingPoint(String val) {
544         switch (val) {
545             case "Infinity":
546             case "-Infinity":
547             case "NaN":
548                 return val;
549         }
550 
551         if (val.indexOf('E') != -1) {
552             return val;
553         }
554 
555         // 1.0 is the only case where a trailing "0" is allowed.
556         // 1.00 is canonicalized as 1.0.
557         int i = val.length() - 1;
558         int d = val.indexOf('.');
559         while (i >= d + 2 && val.charAt(i) == '0') {
560             val = val.substring(0, i--);
561         }
562         return val;
563     }
564 
565     @Override
checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor)566     protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass,
567             JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) {
568         if (ctor.isVarArgs()) {// some method's parameter are variable args
569             ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS;
570         }
571         if (ctor.getModifiers() != ctorDescription.mModifier) {
572             resultObserver.notifyFailure(
573                     FailureType.MISMATCH_METHOD,
574                     ctorDescription.toReadableString(classDescription.getAbsoluteClassName()),
575                     "Non-compatible method found when looking for " +
576                             ctorDescription.toSignatureString());
577         }
578     }
579 
580     @Override
checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffMethod methodDescription, Method method)581     protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass,
582             JDiffClassDescription.JDiffMethod methodDescription, Method method) {
583         // FIXME: A workaround to fix the final mismatch on enumeration
584         if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) {
585             return;
586         }
587 
588         String reason;
589         if ((reason = areMethodsModifierCompatible(
590                 classDescription, methodDescription, method)) != null) {
591             resultObserver.notifyFailure(FailureType.MISMATCH_METHOD,
592                     methodDescription.toReadableString(classDescription.getAbsoluteClassName()),
593                     String.format("Non-compatible method found when looking for %s - because %s",
594                             methodDescription.toSignatureString(), reason));
595         }
596     }
597 
598     /**
599      * Checks to ensure that the modifiers value for two methods are compatible.
600      *
601      * Allowable differences are:
602      *   - the native modifier is ignored
603      *
604      * @param classDescription a description of a class in an API.
605      * @param apiMethod the method read from the api file.
606      * @param reflectedMethod the method found via reflection.
607      * @return null if the method modifiers are compatible otherwise the reason why not.
608      */
areMethodsModifierCompatible( JDiffClassDescription classDescription, JDiffClassDescription.JDiffMethod apiMethod, Method reflectedMethod)609     private static String areMethodsModifierCompatible(
610             JDiffClassDescription classDescription,
611             JDiffClassDescription.JDiffMethod apiMethod,
612             Method reflectedMethod) {
613 
614         // Mask off NATIVE since it is a don't care.
615         // Mask off SYNCHRONIZED since it is not considered API significant (b/112626813)
616         // Mask off STRICT as it has no effect (b/26082535)
617         // Mask off SYNTHETIC, VARARGS and BRIDGE as they are not represented in the API.
618         int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT |
619                 METHOD_MODIFIER_SYNTHETIC | METHOD_MODIFIER_VAR_ARGS | METHOD_MODIFIER_BRIDGE);
620         int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods;
621         int apiModifiers = apiMethod.mModifier & ~ignoredMods;
622 
623         // We can ignore FINAL for classes
624         if ((classDescription.getModifier() & Modifier.FINAL) != 0) {
625             reflectionModifiers &= ~Modifier.FINAL;
626             apiModifiers &= ~Modifier.FINAL;
627         }
628 
629         String genericString = reflectedMethod.toGenericString();
630         if (classDescription.isPreviousApi()) {
631             // If the final and/or abstract modifiers have been removed since the previous API was
632             // published then that is forwards compatible so remove the modifier in the previous API
633             // modifiers so they match the runtime modifiers.
634             apiModifiers = normalizePreviousModifiersIfModifierIsRemoved(
635                     apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
636 
637             if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) {
638                 // Adding the final, or abstract flags to the runtime method is forwards compatible
639                 // as the class cannot be instantiated or extended. Clear the flags for any such
640                 // added modifier from the current implementation's modifiers so that it does not
641                 // cause a mismatch.
642                 reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded(
643                         apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
644             }
645         }
646 
647         if (reflectionModifiers == apiModifiers) {
648             return null;
649         } else {
650             return String.format("modifier mismatch - description (%s), method (%s), for %s",
651                     getModifierString(apiModifiers), getModifierString(reflectionModifiers), genericString);
652         }
653     }
654 
addBaseClass(JDiffClassDescription classDescription)655     public void addBaseClass(JDiffClassDescription classDescription) {
656         // Keep track of all the base interfaces that may by extended.
657         if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) {
658             try {
659                 Class<?> runtimeClass =
660                         ReflectionHelper.findMatchingClass(classDescription, classProvider);
661                 if (runtimeClass == null) {
662                     // Do not treat a missing class from a base API as an error. While it is an
663                     // error it will be caught in the test for the base API so there is no point in
664                     // having this test fail too as it will just create toil for developers and
665                     // testers. Log a message just in case the missing class causes other test
666                     // failures.
667                     LogHelper.loge("Classloader is unable to find base API class "
668                             + classDescription.getAbsoluteClassName(), null);
669                 } else {
670                     interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass);
671                 }
672             } catch (ClassNotFoundException e) {
673                 // Do nothing.
674             }
675         }
676     }
677 }
678