1 /*
2  * Copyright (C) 2018 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 java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.ArrayList;
21 import java.util.Comparator;
22 import java.util.HashSet;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.TreeMap;
28 import java.util.function.Predicate;
29 import java.util.stream.Collectors;
30 import java.util.stream.Stream;
31 
32 /**
33  * Checks that the runtime representation of the interfaces match the API definition.
34  *
35  * <p>Interfaces are treated differently to other classes. Whereas other classes are checked by
36  * making sure that every member in the API is accessible through reflection. Interfaces are
37  * checked to make sure that every method visible through reflection is defined in the API. The
38  * reason for this difference is to ensure that no additional methods have been added to interfaces
39  * that are expected to be implemented by Android developers because that would break backwards
40  * compatibility.
41  *
42  * TODO(b/71886491): This also potentially applies to abstract classes that the App developers are
43  * expected to extend.
44  */
45 class InterfaceChecker {
46 
47     private static final Set<String> HIDDEN_INTERFACE_METHOD_WHITELIST = new HashSet<>();
48     static {
49         // Interfaces that define @hide or @SystemApi or @TestApi methods will by definition contain
50         // methods that do not appear in current.txt. Interfaces added to this
51         // list are probably not meant to be implemented in an application.
52         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
53         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
54         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract java.lang.String android.companion.DeviceFilter.getDeviceDisplayName(D)");
55         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract int android.companion.DeviceFilter.getMediumType()");
56         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
57         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
58         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
59         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
60         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
61         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
62         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
63         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
64         HIDDEN_INTERFACE_METHOD_WHITELIST.add("public abstract android.graphics.Region android.view.WindowManager.getCurrentImeTouchRegion()");
65     }
66 
67     private final ResultObserver resultObserver;
68 
69     private final Map<Class<?>, JDiffClassDescription> class2Description =
70             new TreeMap<>(Comparator.comparing(Class::getName));
71 
72     private final ClassProvider classProvider;
73 
InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider)74     InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
75         this.resultObserver = resultObserver;
76         this.classProvider = classProvider;
77     }
78 
checkQueued()79     public void checkQueued() {
80         for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) {
81             Class<?> runtimeClass = entry.getKey();
82             JDiffClassDescription classDescription = entry.getValue();
83             List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
84             if (methods.size() > 0) {
85                 resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
86                         classDescription.getAbsoluteClassName(), "Interfaces cannot be modified: "
87                                 + classDescription.getAbsoluteClassName() + ": " + methods);
88             }
89         }
90     }
91 
not(Predicate<T> predicate)92     private static <T> Predicate<T> not(Predicate<T> predicate) {
93         return predicate.negate();
94     }
95 
96     /**
97      * Validate that an interfaces method count is as expected.
98      *
99      * @param classDescription the class's API description.
100      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
101      */
checkInterfaceMethodCompliance( JDiffClassDescription classDescription, Class<?> runtimeClass)102     private List<Method> checkInterfaceMethodCompliance(
103             JDiffClassDescription classDescription, Class<?> runtimeClass) {
104 
105         return Stream.of(runtimeClass.getDeclaredMethods())
106                 .filter(not(Method::isDefault))
107                 .filter(not(Method::isSynthetic))
108                 .filter(not(Method::isBridge))
109                 .filter(m -> !Modifier.isStatic(m.getModifiers()))
110                 .filter(m -> !HIDDEN_INTERFACE_METHOD_WHITELIST.contains(m.toGenericString()))
111                 .filter(m -> !findMethod(classDescription, m))
112                 .collect(Collectors.toCollection(ArrayList::new));
113     }
114 
findMethod(JDiffClassDescription classDescription, Method method)115     private boolean findMethod(JDiffClassDescription classDescription, Method method) {
116         Map<Method, String> matchNameNotSignature = new LinkedHashMap<>();
117         for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
118             if (ReflectionHelper.matchesSignature(jdiffMethod, method, matchNameNotSignature)) {
119                 return true;
120             }
121         }
122         for (String interfaceName : classDescription.getImplInterfaces()) {
123             Class<?> interfaceClass = null;
124             try {
125                 interfaceClass = ReflectionHelper.findMatchingClass(interfaceName, classProvider);
126             } catch (ClassNotFoundException e) {
127                 LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
128             }
129 
130             JDiffClassDescription implInterface = class2Description.get(interfaceClass);
131             if (implInterface == null) {
132                 // Class definition is not in the scope of the API definitions.
133                 continue;
134             }
135 
136             if (findMethod(implInterface, method)) {
137                 return true;
138             }
139         }
140         return false;
141     }
142 
143 
queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass)144     void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) {
145 
146         JDiffClassDescription existingDescription = class2Description.get(runtimeClass);
147         if (existingDescription != null) {
148             for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
149                 existingDescription.addMethod(method);
150             }
151         } else {
152             class2Description.put(runtimeClass, classDescription);
153         }
154     }
155 }
156