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