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