Home | History | Annotate | Download | only in cts
      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 
     17 package android.signature.cts;
     18 
     19 import java.lang.reflect.Constructor;
     20 import java.lang.reflect.Executable;
     21 import java.lang.reflect.Field;
     22 import java.lang.reflect.InvocationTargetException;
     23 import java.lang.reflect.Method;
     24 import java.util.List;
     25 
     26 public class DexMemberChecker {
     27 
     28     public interface Observer {
     29         void classAccessible(boolean accessible, DexMember member);
     30         void fieldAccessibleViaReflection(boolean accessible, DexField field);
     31         void fieldAccessibleViaJni(boolean accessible, DexField field);
     32         void methodAccessibleViaReflection(boolean accessible, DexMethod method);
     33         void methodAccessibleViaJni(boolean accessible, DexMethod method);
     34     }
     35 
     36     public static void init() {
     37         System.loadLibrary("cts_dexchecker");
     38     }
     39 
     40     private static void call_VMDebug_allowHiddenApiReflectionFrom(Class<?> klass) throws Exception {
     41       Method method = null;
     42 
     43       try {
     44         Class<?> vmdebug = Class.forName("dalvik.system.VMDebug");
     45         method = vmdebug.getDeclaredMethod("allowHiddenApiReflectionFrom", Class.class);
     46       } catch (Exception ex) {
     47         // Could not find the method. Report the problem as a RuntimeException.
     48         throw new RuntimeException(ex);
     49       }
     50 
     51       try {
     52         method.invoke(null, klass);
     53       } catch (InvocationTargetException ex) {
     54         // Rethrow the original exception.
     55         Throwable cause = ex.getCause();
     56         // Please the compiler's 'throws' static analysis.
     57         if (cause instanceof Exception) {
     58           throw (Exception) cause;
     59         } else {
     60           throw (Error) cause;
     61         }
     62       }
     63     }
     64 
     65     public static boolean requestExemptionFromHiddenApiChecks() throws Exception {
     66       try {
     67         call_VMDebug_allowHiddenApiReflectionFrom(DexMemberChecker.class);
     68         return true;
     69       } catch (SecurityException ex) {
     70         return false;
     71       }
     72     }
     73 
     74     public static void checkSingleMember(DexMember dexMember, DexMemberChecker.Observer observer) {
     75         Class<?> klass = findClass(dexMember);
     76         if (klass == null) {
     77             // Class not found. Therefore its members are not visible.
     78             observer.classAccessible(false, dexMember);
     79             return;
     80         }
     81         observer.classAccessible(true, dexMember);
     82 
     83         if (dexMember instanceof DexField) {
     84             DexField field = (DexField) dexMember;
     85             observer.fieldAccessibleViaReflection(
     86                     hasMatchingField_Reflection(klass, field),
     87                     field);
     88             observer.fieldAccessibleViaJni(
     89                     hasMatchingField_JNI(klass, field),
     90                     field);
     91         } else if (dexMember instanceof DexMethod) {
     92             DexMethod method = (DexMethod) dexMember;
     93             observer.methodAccessibleViaReflection(
     94                     hasMatchingMethod_Reflection(klass, method),
     95                     method);
     96             observer.methodAccessibleViaJni(
     97                     hasMatchingMethod_JNI(klass, method),
     98                     method);
     99         } else {
    100             throw new IllegalStateException("Unexpected type of dex member");
    101         }
    102     }
    103 
    104     private static boolean typesMatch(Class<?>[] classes, List<String> typeNames) {
    105         if (classes.length != typeNames.size()) {
    106             return false;
    107         }
    108         for (int i = 0; i < classes.length; ++i) {
    109             if (!classes[i].getTypeName().equals(typeNames.get(i))) {
    110                 return false;
    111             }
    112         }
    113         return true;
    114     }
    115 
    116     private static Class<?> findClass(DexMember dexMember) {
    117         try {
    118             return Class.forName(dexMember.getJavaClassName());
    119         } catch (ClassNotFoundException ex) {
    120             return null;
    121         }
    122     }
    123 
    124     private static boolean hasMatchingField_Reflection(Class<?> klass, DexField dexField) {
    125         try {
    126             klass.getDeclaredField(dexField.getName());
    127             return true;
    128         } catch (NoSuchFieldException ex) {
    129             return false;
    130         }
    131     }
    132 
    133     private static boolean hasMatchingField_JNI(Class<?> klass, DexField dexField) {
    134         Field ifield = getField_JNI(klass, dexField.getName(), dexField.getDexType());
    135         Field sfield = getStaticField_JNI(klass, dexField.getName(), dexField.getDexType());
    136         return (ifield != null && ifield.getDeclaringClass() == klass) ||
    137                (sfield != null && sfield.getDeclaringClass() == klass);
    138     }
    139 
    140     private static boolean hasMatchingMethod_Reflection(Class<?> klass, DexMethod dexMethod) {
    141         List<String> methodParams = dexMethod.getJavaParameterTypes();
    142 
    143         if (dexMethod.isConstructor()) {
    144             for (Constructor constructor : klass.getDeclaredConstructors()) {
    145                 if (typesMatch(constructor.getParameterTypes(), methodParams)) {
    146                     return true;
    147                 }
    148             }
    149         } else {
    150             String methodReturnType = dexMethod.getJavaType();
    151             for (Method method : klass.getDeclaredMethods()) {
    152                 if (method.getName().equals(dexMethod.getName())
    153                         && method.getReturnType().getTypeName().equals(methodReturnType)
    154                         && typesMatch(method.getParameterTypes(), methodParams)) {
    155                     return true;
    156                 }
    157             }
    158         }
    159         return false;
    160     }
    161 
    162     private static boolean hasMatchingMethod_JNI(Class<?> klass, DexMethod dexMethod) {
    163         Executable imethod = getMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
    164         Executable smethod = dexMethod.isConstructor() ? null :
    165              getStaticMethod_JNI(klass, dexMethod.getName(), dexMethod.getDexSignature());
    166         return (imethod != null && imethod.getDeclaringClass() == klass) ||
    167                (smethod != null && smethod.getDeclaringClass() == klass);
    168     }
    169 
    170     private static native Field getField_JNI(Class<?> klass, String name, String type);
    171     private static native Field getStaticField_JNI(Class<?> klass, String name, String type);
    172     private static native Executable getMethod_JNI(Class<?> klass, String name, String signature);
    173     private static native Executable getStaticMethod_JNI(Class<?> klass, String name,
    174             String signature);
    175 
    176 }
    177