• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 the final and/or abstract modifiers have been removed since the previous API was
276         // published then that is forwards compatible so remove the modifier in the previous API
277         // modifiers so they match the runtime modifiers.
278         apiModifiers =
279                 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 =
288                     normalizeCurrentModifiersIfModifierIsAdded(
289                             apiModifiers,
290                             reflectionModifiers,
291                             Modifier.FINAL,
292                             Modifier.ABSTRACT,
293                             Modifier.STATIC);
294         }
295 
296         if ((reflectionModifiers == apiModifiers)
297                 && (classDescription.isEnumType() == runtimeClass.isEnum())) {
298             return null;
299         } else {
300             return String.format("modifier mismatch - description (%s), class (%s)",
301                     getModifierString(apiModifiers), getModifierString(reflectionModifiers));
302         }
303     }
304 
305     /**
306      * Checks if the class under test is compliant with regards to
307      * annnotations when compared to the API.
308      *
309      * @param classDescription a description of a class in an API.
310      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
311      * @return true if the class is compliant
312      */
checkClassAnnotationCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)313     private static boolean checkClassAnnotationCompliance(JDiffClassDescription classDescription,
314             Class<?> runtimeClass) {
315         if (runtimeClass.isAnnotation()) {
316             // check annotation
317             for (String inter : classDescription.getImplInterfaces()) {
318                 if ("java.lang.annotation.Annotation".equals(inter)) {
319                     return true;
320                 }
321             }
322             return false;
323         }
324         return true;
325     }
326 
327     /**
328      * Checks if the class under test extends the proper classes
329      * according to the API.
330      *
331      * @param classDescription a description of a class in an API.
332      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
333      * @return true if the class is compliant.
334      */
checkClassExtendsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)335     private static boolean checkClassExtendsCompliance(JDiffClassDescription classDescription,
336             Class<?> runtimeClass) {
337         // Nothing to check if it doesn't extend anything.
338         if (classDescription.getExtendedClass() != null) {
339             Class<?> superClass = runtimeClass.getSuperclass();
340 
341             while (superClass != null) {
342                 if (superClass.getCanonicalName().equals(classDescription.getExtendedClass())) {
343                     return true;
344                 }
345                 superClass = superClass.getSuperclass();
346             }
347             // Couldn't find a matching superclass.
348             return false;
349         }
350         return true;
351     }
352 
353     /**
354      * Checks if the class under test implements the proper interfaces
355      * according to the API.
356      *
357      * @param classDescription a description of a class in an API.
358      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
359      * @return true if the class is compliant
360      */
checkClassImplementsCompliance(JDiffClassDescription classDescription, Class<?> runtimeClass)361     private static boolean checkClassImplementsCompliance(JDiffClassDescription classDescription,
362             Class<?> runtimeClass) {
363         Set<String> interFaceSet = new HashSet<>();
364 
365         addInterfacesToSetByName(runtimeClass, interFaceSet);
366 
367         for (String inter : classDescription.getImplInterfaces()) {
368             if (!interFaceSet.contains(inter)) {
369                 return false;
370             }
371         }
372         return true;
373     }
374 
addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet)375     private static void addInterfacesToSetByName(Class<?> runtimeClass, Set<String> interFaceSet) {
376         Class<?>[] interfaces = runtimeClass.getInterfaces();
377         for (Class<?> c : interfaces) {
378             interFaceSet.add(c.getCanonicalName());
379             // Add grandparent interfaces in case the parent interface is hidden.
380             addInterfacesToSetByName(c, interFaceSet);
381         }
382 
383         // Add the interfaces that the super class implements as well just in case the super class
384         // is hidden.
385         Class<?> superClass = runtimeClass.getSuperclass();
386         if (superClass != null) {
387             addInterfacesToSetByName(superClass, interFaceSet);
388         }
389     }
390 
391     @Override
checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffField fieldDescription, Field field)392     protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass,
393             JDiffField fieldDescription, Field field) {
394         int expectedModifiers = fieldDescription.mModifier;
395         int actualModifiers = field.getModifiers();
396         if (actualModifiers != expectedModifiers) {
397             resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
398                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
399                     String.format(
400                             "Incompatible field modifiers, expected %s, found %s",
401                             getModifierString(expectedModifiers),
402                             getModifierString(actualModifiers)));
403         }
404 
405         String expectedFieldType = fieldDescription.mFieldType;
406         String actualFieldType = ReflectionHelper.typeToString(field.getGenericType());
407         if (!DefaultTypeComparator.INSTANCE.compare(expectedFieldType, actualFieldType)) {
408             resultObserver.notifyFailure(
409                     FailureType.MISMATCH_FIELD,
410                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
411                     String.format("Incompatible field type found, expected %s, found %s",
412                             expectedFieldType, actualFieldType));
413         }
414 
415         String message = checkFieldValueCompliance(classDescription, fieldDescription, field);
416         if (message != null) {
417             resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
418                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
419                     message);
420         }
421     }
422 
423     private static final int BRIDGE    = 0x00000040;
424     private static final int VARARGS   = 0x00000080;
425     private static final int SYNTHETIC = 0x00001000;
426     private static final int ANNOTATION  = 0x00002000;
427     private static final int ENUM      = 0x00004000;
428     private static final int MANDATED  = 0x00008000;
429 
getModifierString(int modifiers)430     private static String getModifierString(int modifiers) {
431         Formatter formatter = new Formatter();
432         String m = Modifier.toString(modifiers);
433         formatter.format("<%s", m);
434         String sep = m.isEmpty() ? "" : " ";
435         if ((modifiers & BRIDGE) != 0) {
436             formatter.format("%senum", sep);
437             sep = " ";
438         }
439         if ((modifiers & VARARGS) != 0) {
440             formatter.format("%svarargs", sep);
441             sep = " ";
442         }
443         if ((modifiers & SYNTHETIC) != 0) {
444             formatter.format("%ssynthetic", sep);
445             sep = " ";
446         }
447         if ((modifiers & ANNOTATION) != 0) {
448             formatter.format("%sannotation", sep);
449             sep = " ";
450         }
451         if ((modifiers & ENUM) != 0) {
452             formatter.format("%senum", sep);
453             sep = " ";
454         }
455         if ((modifiers & MANDATED) != 0) {
456             formatter.format("%smandated", sep);
457         }
458         return formatter.format("> (0x%x)", modifiers).toString();
459     }
460 
461     /**
462      * Checks whether the field values are compatible.
463      *
464      * @param apiField The field as defined by the platform API.
465      * @param deviceField The field as defined by the device under test.
466      */
checkFieldValueCompliance( JDiffClassDescription classDescription, JDiffField apiField, Field deviceField)467     private static String checkFieldValueCompliance(
468             JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) {
469         if ((apiField.mModifier & Modifier.FINAL) == 0 ||
470                 (apiField.mModifier & Modifier.STATIC) == 0) {
471             // Only final static fields can have fixed values.
472             return null;
473         }
474         String apiFieldValue = apiField.getValueString();
475         if (apiFieldValue == null) {
476             // If we don't define a constant value for it, then it can be anything.
477             return null;
478         }
479 
480         // Convert char into a number to match the value returned from device field. The device
481         // field does not
482         if (deviceField.getType() == char.class) {
483             apiFieldValue = convertCharToCanonicalValue(apiFieldValue.charAt(0));
484         }
485 
486         String deviceFieldValue = getFieldValueAsString(deviceField);
487         if (!Objects.equals(apiFieldValue, deviceFieldValue)) {
488             String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName());
489             if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName)
490                     && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals(
491                             apiFieldValue)
492                     && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals(
493                             deviceFieldValue)) {
494                 return null;
495             }
496             return String.format("Incorrect field value, expected <%s>, found <%s>",
497                     apiFieldValue, deviceFieldValue);
498 
499         }
500 
501         return null;
502     }
503 
getFieldValueAsString(Field deviceField)504     private static String getFieldValueAsString(Field deviceField) {
505         // Some fields may be protected or package-private
506         deviceField.setAccessible(true);
507         try {
508             Class<?> fieldType = deviceField.getType();
509             if (fieldType == byte.class) {
510                 return Byte.toString(deviceField.getByte(null));
511             } else if (fieldType == char.class) {
512                 return convertCharToCanonicalValue(deviceField.getChar(null));
513             } else if (fieldType == short.class) {
514                 return  Short.toString(deviceField.getShort(null));
515             } else if (fieldType == int.class) {
516                 return  Integer.toString(deviceField.getInt(null));
517             } else if (fieldType == long.class) {
518                 return Long.toString(deviceField.getLong(null));
519             } else if (fieldType == float.class) {
520                 return  canonicalizeFloatingPoint(
521                                 Float.toString(deviceField.getFloat(null)));
522             } else if (fieldType == double.class) {
523                 return  canonicalizeFloatingPoint(
524                                 Double.toString(deviceField.getDouble(null)));
525             } else if (fieldType == boolean.class) {
526                 return  Boolean.toString(deviceField.getBoolean(null));
527             } else if (fieldType == java.lang.String.class) {
528                 return (String) deviceField.get(null);
529             } else {
530                 return null;
531             }
532         } catch (IllegalAccessException e) {
533             throw new RuntimeException(e);
534         }
535     }
536 
convertCharToCanonicalValue(char c)537     private static String convertCharToCanonicalValue(char c) {
538         return String.format("'%c' (0x%x)", c, (int) c);
539     }
540 
541     /**
542      * Canonicalize the string representation of floating point numbers.
543      *
544      * This needs to be kept in sync with the doclava canonicalization.
545      */
canonicalizeFloatingPoint(String val)546     private static String canonicalizeFloatingPoint(String val) {
547         switch (val) {
548             case "Infinity":
549             case "-Infinity":
550             case "NaN":
551                 return val;
552         }
553 
554         if (val.indexOf('E') != -1) {
555             return val;
556         }
557 
558         // 1.0 is the only case where a trailing "0" is allowed.
559         // 1.00 is canonicalized as 1.0.
560         int i = val.length() - 1;
561         int d = val.indexOf('.');
562         while (i >= d + 2 && val.charAt(i) == '0') {
563             val = val.substring(0, i--);
564         }
565         return val;
566     }
567 
568     @Override
checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor)569     protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass,
570             JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) {
571         if (ctor.isVarArgs()) {// some method's parameter are variable args
572             ctorDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS;
573         }
574         if (ctor.getModifiers() != ctorDescription.mModifier) {
575             resultObserver.notifyFailure(
576                     FailureType.MISMATCH_METHOD,
577                     ctorDescription.toReadableString(classDescription.getAbsoluteClassName()),
578                     "Non-compatible method found when looking for " +
579                             ctorDescription.toSignatureString());
580         }
581     }
582 
583     @Override
checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, JDiffClassDescription.JDiffMethod methodDescription, Method method)584     protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass,
585             JDiffClassDescription.JDiffMethod methodDescription, Method method) {
586         // FIXME: A workaround to fix the final mismatch on enumeration
587         if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) {
588             return;
589         }
590 
591         String reason;
592         if ((reason = areMethodsModifierCompatible(
593                 classDescription, methodDescription, method)) != null) {
594             resultObserver.notifyFailure(FailureType.MISMATCH_METHOD,
595                     methodDescription.toReadableString(classDescription.getAbsoluteClassName()),
596                     String.format("Non-compatible method found when looking for %s - because %s",
597                             methodDescription.toSignatureString(), reason));
598         }
599     }
600 
601     /**
602      * Checks to ensure that the modifiers value for two methods are compatible.
603      *
604      * Allowable differences are:
605      *   - the native modifier is ignored
606      *
607      * @param classDescription a description of a class in an API.
608      * @param apiMethod the method read from the api file.
609      * @param reflectedMethod the method found via reflection.
610      * @return null if the method modifiers are compatible otherwise the reason why not.
611      */
areMethodsModifierCompatible( JDiffClassDescription classDescription, JDiffClassDescription.JDiffMethod apiMethod, Method reflectedMethod)612     private static String areMethodsModifierCompatible(
613             JDiffClassDescription classDescription,
614             JDiffClassDescription.JDiffMethod apiMethod,
615             Method reflectedMethod) {
616 
617         // Mask off NATIVE since it is a don't care.
618         // Mask off SYNCHRONIZED since it is not considered API significant (b/112626813)
619         // Mask off STRICT as it has no effect (b/26082535)
620         // Mask off SYNTHETIC, VARARGS and BRIDGE as they are not represented in the API.
621         int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT |
622                 METHOD_MODIFIER_SYNTHETIC | METHOD_MODIFIER_VAR_ARGS | METHOD_MODIFIER_BRIDGE);
623         int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods;
624         int apiModifiers = apiMethod.mModifier & ~ignoredMods;
625 
626         // We can ignore FINAL for classes
627         if ((classDescription.getModifier() & Modifier.FINAL) != 0) {
628             reflectionModifiers &= ~Modifier.FINAL;
629             apiModifiers &= ~Modifier.FINAL;
630         }
631 
632         String genericString = reflectedMethod.toGenericString();
633 
634         // If the final and/or abstract modifiers have been removed since the previous API was
635         // published then that is forwards compatible so remove the modifier in the previous API
636         // modifiers so they match the runtime modifiers.
637         apiModifiers =
638                 normalizePreviousModifiersIfModifierIsRemoved(
639                         apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
640 
641         if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) {
642             // Adding the final, or abstract flags to the runtime method is forwards compatible
643             // as the class cannot be instantiated or extended. Clear the flags for any such
644             // added modifier from the current implementation's modifiers so that it does not
645             // cause a mismatch.
646             reflectionModifiers =
647                     normalizeCurrentModifiersIfModifierIsAdded(
648                             apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
649         }
650 
651         if (reflectionModifiers == apiModifiers) {
652             return null;
653         } else {
654             return String.format("modifier mismatch - description (%s), method (%s), for %s",
655                     getModifierString(apiModifiers), getModifierString(reflectionModifiers), genericString);
656         }
657     }
658 
addBaseClass(JDiffClassDescription classDescription)659     public void addBaseClass(JDiffClassDescription classDescription) {
660         // Keep track of all the base interfaces that may by extended.
661         if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) {
662             try {
663                 Class<?> runtimeClass =
664                         ReflectionHelper.findMatchingClass(classDescription, classProvider);
665                 if (runtimeClass == null) {
666                     // Do not treat a missing class from a base API as an error. While it is an
667                     // error it will be caught in the test for the base API so there is no point in
668                     // having this test fail too as it will just create toil for developers and
669                     // testers. Log a message just in case the missing class causes other test
670                     // failures.
671                     LogHelper.loge("Classloader is unable to find base API class "
672                             + classDescription.getAbsoluteClassName(), null);
673                 } else {
674                     interfaceChecker.queueForDeferredCheck(classDescription, runtimeClass);
675                 }
676             } catch (ClassNotFoundException e) {
677                 // Do nothing.
678             }
679         }
680     }
681 }
682