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