1/*
2 * Copyright (C) 2015 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
17import java.lang.reflect.Field;
18import java.lang.reflect.Method;
19import java.util.List;
20
21class MyClassLoader extends ClassLoader {
22  MyClassLoader() throws Exception {
23    super(MyClassLoader.class.getClassLoader());
24
25    // Some magic to get access to the pathList field of BaseDexClassLoader.
26    ClassLoader loader = getClass().getClassLoader();
27    Class<?> baseDexClassLoader = loader.getClass().getSuperclass();
28    Field f = baseDexClassLoader.getDeclaredField("pathList");
29    f.setAccessible(true);
30    Object pathList = f.get(loader);
31
32    // Some magic to get access to the dexField field of pathList.
33    f = pathList.getClass().getDeclaredField("dexElements");
34    f.setAccessible(true);
35    dexElements = (Object[]) f.get(pathList);
36    dexFileField = dexElements[0].getClass().getDeclaredField("dexFile");
37    dexFileField.setAccessible(true);
38  }
39
40  Object[] dexElements;
41  Field dexFileField;
42
43  static ClassLoader level1ClassLoader;
44
45  protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
46    if (this != level1ClassLoader) {
47      if (className.equals("Level1")) {
48        return level1ClassLoader.loadClass(className);
49      } else if (className.equals("Level2")) {
50        throw new ClassNotFoundException("None of my methods require Level2!");
51      } else if (!className.equals("LoadedByMyClassLoader")) {
52        // We're only going to handle LoadedByMyClassLoader.
53        return getParent().loadClass(className);
54      }
55    } else {
56      if (className != "Level1" && className != "Level2") {
57        return getParent().loadClass(className);
58      }
59    }
60
61    // Mimic what DexPathList.findClass is doing.
62    try {
63      for (Object element : dexElements) {
64        Object dex = dexFileField.get(element);
65        Method method = dex.getClass().getDeclaredMethod(
66            "loadClassBinaryName", String.class, ClassLoader.class, List.class);
67
68        if (dex != null) {
69          Class clazz = (Class)method.invoke(dex, className, this, null);
70          if (clazz != null) {
71            return clazz;
72          }
73        }
74      }
75    } catch (Exception e) { /* Ignore */ }
76    return null;
77  }
78}
79
80class LoadedByMyClassLoader {
81  public static void bar() {
82    Level1.$inline$bar();
83  }
84}
85
86class Main {
87  public static void main(String[] args) throws Exception {
88    System.loadLibrary(args[0]);
89    // Clone resolved methods, to restore the original version just
90    // before we walk the stack in $noinline$bar.
91    savedResolvedMethods = cloneResolvedMethods(Main.class);
92
93    MyClassLoader o = new MyClassLoader();
94    MyClassLoader.level1ClassLoader = new MyClassLoader();
95    Class foo = o.loadClass("LoadedByMyClassLoader");
96    Method m = foo.getDeclaredMethod("bar");
97    try {
98      m.invoke(null);
99    } catch (Error e) { /* Ignore */ }
100  }
101
102  public static void $inline$bar() {
103  }
104
105  public static void $noinline$bar() {
106    try {
107      // Be evil and clear all dex cache entries.
108      Field f = Class.class.getDeclaredField("dexCache");
109      f.setAccessible(true);
110      Object dexCache = f.get(Main.class);
111      f = dexCache.getClass().getDeclaredField("resolvedTypes");
112      f.setAccessible(true);
113      Object[] array = (Object[]) f.get(dexCache);
114      for (int i = 0; i < array.length; i++) {
115        array[i] = null;
116      }
117      restoreResolvedMethods(Main.class, savedResolvedMethods);
118    } catch (Throwable t) { /* Ignore */ }
119
120    // This will walk the stack, trying to resolve methods in it.
121    // Because we cleared dex cache entries, we will have to find
122    // classes again, which require to use the correct class loader
123    // in the presence of inlining.
124    new Exception().printStackTrace();
125  }
126  static Object savedResolvedMethods;
127
128  static native Object cloneResolvedMethods(Class<?> cls);
129  static native void restoreResolvedMethods(Class<?> cls, Object saved);
130}
131