Main.java revision 7257be3512c8c8b6cb66ce868c111fbbb4106123
1/*
2 * Copyright (C) 2017 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 dalvik.system.InMemoryDexClassLoader;
18import dalvik.system.PathClassLoader;
19import java.io.File;
20import java.io.InputStream;
21import java.lang.reflect.Constructor;
22import java.lang.reflect.Method;
23import java.nio.ByteBuffer;
24import java.nio.file.Files;
25import java.util.Arrays;
26import java.util.zip.ZipEntry;
27import java.util.zip.ZipFile;
28
29public class Main {
30  public static void main(String[] args) throws Exception {
31    System.loadLibrary(args[0]);
32    prepareNativeLibFileName(args[0]);
33
34    // Run test with both parent and child dex files loaded with class loaders.
35    // The expectation is that hidden members in parent should be visible to
36    // the child.
37    doTest(false, false);
38    doUnloading();
39
40    // Now append parent dex file to boot class path and run again. This time
41    // the child dex file should not be able to access private APIs of the parent.
42    appendToBootClassLoader(DEX_PARENT_BOOT);
43    doTest(true, false);
44    doUnloading();
45
46    // And finally append to child to boot class path as well. With both in the
47    // boot class path, access should be granted.
48    appendToBootClassLoader(DEX_CHILD);
49    doTest(true, true);
50    doUnloading();
51  }
52
53  private static void doTest(boolean parentInBoot, boolean childInBoot) throws Exception {
54    // Load parent dex if it is not in boot class path.
55    ClassLoader parentLoader = null;
56    if (parentInBoot) {
57      parentLoader = BOOT_CLASS_LOADER;
58    } else {
59      parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
60    }
61
62    // Load child dex if it is not in boot class path.
63    ClassLoader childLoader = null;
64    if (childInBoot) {
65      if (parentLoader != BOOT_CLASS_LOADER) {
66        throw new IllegalStateException(
67            "DeclaringClass must be in parent class loader of CallingClass");
68      }
69      childLoader = BOOT_CLASS_LOADER;
70    } else {
71      childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
72    }
73
74    // Create a unique copy of the native library. Each shared library can only
75    // be loaded once, but for some reason even classes from a class loader
76    // cannot register their native methods against symbols in a shared library
77    // loaded by their parent class loader.
78    String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot);
79
80    // Invoke ChildClass.runTest
81    Class.forName("ChildClass", true, childLoader)
82        .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE)
83            .invoke(null, nativeLibCopy, parentInBoot, childInBoot);
84  }
85
86  // Routine which tries to figure out the absolute path of our native library.
87  private static void prepareNativeLibFileName(String arg) throws Exception {
88    String libName = System.mapLibraryName(arg);
89    Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
90    libPathsMethod.setAccessible(true);
91    String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
92    nativeLibFileName = null;
93    for (String p : libPaths) {
94      String candidate = p + libName;
95      if (new File(candidate).exists()) {
96        nativeLibFileName = candidate;
97        break;
98      }
99    }
100    if (nativeLibFileName == null) {
101      throw new IllegalStateException("Didn't find " + libName + " in " +
102          Arrays.toString(libPaths));
103    }
104  }
105
106  // Helper to read dex file into memory.
107  private static ByteBuffer readDexFile(String jarFileName) throws Exception {
108    ZipFile zip = new ZipFile(new File(jarFileName));
109    ZipEntry entry = zip.getEntry("classes.dex");
110    InputStream is = zip.getInputStream(entry);
111    int offset = 0;
112    int size = (int) entry.getSize();
113    ByteBuffer buffer = ByteBuffer.allocate(size);
114    while (is.available() > 0) {
115      is.read(buffer.array(), offset, size - offset);
116    }
117    is.close();
118    zip.close();
119    return buffer;
120  }
121
122  // Copy native library to a new file with a unique name so it does not conflict
123  // with other loaded instance of the same binary file.
124  private static String createNativeLibCopy(boolean parentInBoot, boolean childInBoot)
125      throws Exception {
126    String tempFileName = System.mapLibraryName(
127        "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0"));
128    File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
129    Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
130    return tempFile.getAbsolutePath();
131  }
132
133  private static void doUnloading() {
134    // Do multiple GCs to prevent rare flakiness if some other thread is keeping the
135    // classloader live.
136    for (int i = 0; i < 5; ++i) {
137       Runtime.getRuntime().gc();
138    }
139  }
140
141  private static String nativeLibFileName;
142
143  private static final String DEX_PARENT =
144      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath();
145  private static final String DEX_PARENT_BOOT =
146      new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
147  private static final String DEX_CHILD =
148      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath();
149
150  private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
151
152  private static native void appendToBootClassLoader(String dexPath);
153}
154