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