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