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.api;
18 
19 import android.os.Debug;
20 import android.util.Log;
21 import android.signature.cts.ClassProvider;
22 import android.signature.cts.DexField;
23 import android.signature.cts.DexMethod;
24 import android.signature.cts.DexMember;
25 import dalvik.system.BaseDexClassLoader;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.util.Arrays;
33 import java.util.Objects;
34 import java.util.stream.Stream;
35 
36 @SuppressWarnings("deprecation")
37 public class BootClassPathClassesProvider extends ClassProvider {
38     private static final String TAG = "BootClassPathClassesProvider";
39 
40     private static boolean sJvmtiAttached = false;
41 
42     @Override
getAllClasses()43     public Stream<Class<?>> getAllClasses() {
44         maybeAttachJvmtiAgent();
45         return (Stream<Class<?>>)
46             Arrays.stream(getClassloaderDescriptors(Object.class.getClassLoader()))
47                 .map(descriptor -> {
48                     String classname = descriptor.replace('/', '.');
49                     // omit L and ; at the front and at the end
50                     return classname.substring(1, classname.length() - 1);
51                 })
52                 .map(classname -> {
53                     try {
54                         return getClass(classname);
55                     } catch (ClassNotFoundException e) {
56                         // It could be that a class failed to verify.
57                         // No process will be able to load it, so it's ok to silently ignore.
58                         return null;
59                     } catch (NoClassDefFoundError e) {
60                         Log.w(TAG, "Could not load class " + classname, e);
61                         return null;
62                     }
63                 })
64                 .filter(Objects::nonNull);
65     }
66 
67     @Override
getAllMembers(Class<?> klass)68     public Stream<DexMember> getAllMembers(Class<?> klass) {
69         maybeAttachJvmtiAgent();
70 
71         String[][] field_infos = getClassMemberNamesAndTypes(klass, /* fields */ true);
72         String[][] method_infos = getClassMemberNamesAndTypes(klass, /* fields */ false);
73         if (field_infos.length != 2 || field_infos[0].length != field_infos[1].length ||
74             method_infos.length != 2 || method_infos[0].length != method_infos[1].length) {
75           throw new RuntimeException("Invalid result from getClassMemberNamesAndTypes");
76         }
77 
78         String klass_desc = "L" + klass.getName().replace('.', '/') + ";";
79         DexMember[] members = new DexMember[field_infos[0].length + method_infos[0].length];
80         for (int i = 0; i < field_infos[0].length; i++) {
81             members[i] = new DexField(klass_desc, field_infos[0][i], field_infos[1][i], null);
82         }
83         for (int i = 0; i < method_infos[0].length; i++) {
84             members[i + field_infos[0].length] =
85                 new DexMethod(klass_desc, method_infos[0][i], method_infos[1][i], null);
86         }
87         return Arrays.stream(members);
88     }
89 
maybeAttachJvmtiAgent()90     private static void maybeAttachJvmtiAgent() {
91       if (!sJvmtiAttached) {
92           try {
93               Debug.attachJvmtiAgent(copyAgentToFile("classdescriptors").getAbsolutePath(), null,
94                       BootClassPathClassesProvider.class.getClassLoader());
95               sJvmtiAttached = true;
96               initialize();
97           } catch (Exception e) {
98               throw new RuntimeException("Error while attaching JVMTI agent", e);
99           }
100       }
101     }
102 
copyAgentToFile(String lib)103     private static File copyAgentToFile(String lib) throws Exception {
104         ClassLoader cl = BootClassPathClassesProvider.class.getClassLoader();
105 
106         File copiedAgent = File.createTempFile("agent", ".so");
107         try (InputStream is = new FileInputStream(
108                 ((BaseDexClassLoader) cl).findLibrary(lib))) {
109             try (OutputStream os = new FileOutputStream(copiedAgent)) {
110                 byte[] buffer = new byte[64 * 1024];
111 
112                 while (true) {
113                     int numRead = is.read(buffer);
114                     if (numRead == -1) {
115                         break;
116                     }
117                     os.write(buffer, 0, numRead);
118                 }
119             }
120         }
121         return copiedAgent;
122     }
123 
initialize()124     private static native void initialize();
125 
getClassloaderDescriptors(ClassLoader loader)126     private static native String[] getClassloaderDescriptors(ClassLoader loader);
getClassMemberNamesAndTypes(Class<?> klass, boolean getFields)127     private static native String[][] getClassMemberNamesAndTypes(Class<?> klass, boolean getFields);
128 }
129