1 /* 2 * Copyright (C) 2011 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 17 package dalvik.system; 18 19 import java.io.File; 20 import java.net.URL; 21 import java.nio.ByteBuffer; 22 import java.util.ArrayList; 23 import java.util.Collection; 24 import java.util.Enumeration; 25 import java.util.List; 26 27 /** 28 * Base class for common functionality between various dex-based 29 * {@link ClassLoader} implementations. 30 */ 31 public class BaseDexClassLoader extends ClassLoader { 32 33 /** 34 * Hook for customizing how dex files loads are reported. 35 * 36 * This enables the framework to monitor the use of dex files. The 37 * goal is to simplify the mechanism for optimizing foreign dex files and 38 * enable further optimizations of secondary dex files. 39 * 40 * The reporting happens only when new instances of BaseDexClassLoader 41 * are constructed and will be active only after this field is set with 42 * {@link BaseDexClassLoader#setReporter}. 43 */ 44 /* @NonNull */ private static volatile Reporter reporter = null; 45 46 private final DexPathList pathList; 47 48 /** 49 * Constructs an instance. 50 * Note that all the *.jar and *.apk files from {@code dexPath} might be 51 * first extracted in-memory before the code is loaded. This can be avoided 52 * by passing raw dex files (*.dex) in the {@code dexPath}. 53 * 54 * @param dexPath the list of jar/apk files containing classes and 55 * resources, delimited by {@code File.pathSeparator}, which 56 * defaults to {@code ":"} on Android. 57 * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26. 58 * @param librarySearchPath the list of directories containing native 59 * libraries, delimited by {@code File.pathSeparator}; may be 60 * {@code null} 61 * @param parent the parent class loader 62 */ BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)63 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 64 String librarySearchPath, ClassLoader parent) { 65 this(dexPath, optimizedDirectory, librarySearchPath, parent, false); 66 } 67 68 /** 69 * @hide 70 */ BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted)71 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 72 String librarySearchPath, ClassLoader parent, boolean isTrusted) { 73 super(parent); 74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); 75 76 if (reporter != null) { 77 reportClassLoaderChain(); 78 } 79 } 80 81 /** 82 * Reports the current class loader chain to the registered {@code reporter}. 83 * The chain is reported only if all its elements are {@code BaseDexClassLoader}. 84 */ reportClassLoaderChain()85 private void reportClassLoaderChain() { 86 ArrayList<BaseDexClassLoader> classLoadersChain = new ArrayList<>(); 87 ArrayList<String> classPaths = new ArrayList<>(); 88 89 classLoadersChain.add(this); 90 classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths())); 91 92 boolean onlySawSupportedClassLoaders = true; 93 ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent(); 94 ClassLoader current = getParent(); 95 96 while (current != null && current != bootClassLoader) { 97 if (current instanceof BaseDexClassLoader) { 98 BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current; 99 classLoadersChain.add(bdcCurrent); 100 classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths())); 101 } else { 102 onlySawSupportedClassLoaders = false; 103 break; 104 } 105 current = current.getParent(); 106 } 107 108 if (onlySawSupportedClassLoaders) { 109 reporter.report(classLoadersChain, classPaths); 110 } 111 } 112 113 /** 114 * Constructs an instance. 115 * 116 * dexFile must be an in-memory representation of a full dexFile. 117 * 118 * @param dexFiles the array of in-memory dex files containing classes. 119 * @param parent the parent class loader 120 * 121 * @hide 122 */ BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent)123 public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) { 124 // TODO We should support giving this a library search path maybe. 125 super(parent); 126 this.pathList = new DexPathList(this, dexFiles); 127 } 128 129 @Override findClass(String name)130 protected Class<?> findClass(String name) throws ClassNotFoundException { 131 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 132 Class c = pathList.findClass(name, suppressedExceptions); 133 if (c == null) { 134 ClassNotFoundException cnfe = new ClassNotFoundException( 135 "Didn't find class \"" + name + "\" on path: " + pathList); 136 for (Throwable t : suppressedExceptions) { 137 cnfe.addSuppressed(t); 138 } 139 throw cnfe; 140 } 141 return c; 142 } 143 144 /** 145 * @hide 146 */ addDexPath(String dexPath)147 public void addDexPath(String dexPath) { 148 addDexPath(dexPath, false /*isTrusted*/); 149 } 150 151 /** 152 * @hide 153 */ addDexPath(String dexPath, boolean isTrusted)154 public void addDexPath(String dexPath, boolean isTrusted) { 155 pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted); 156 } 157 158 /** 159 * Adds additional native paths for consideration in subsequent calls to 160 * {@link #findLibrary(String)} 161 * @hide 162 */ addNativePath(Collection<String> libPaths)163 public void addNativePath(Collection<String> libPaths) { 164 pathList.addNativePath(libPaths); 165 } 166 167 @Override findResource(String name)168 protected URL findResource(String name) { 169 return pathList.findResource(name); 170 } 171 172 @Override findResources(String name)173 protected Enumeration<URL> findResources(String name) { 174 return pathList.findResources(name); 175 } 176 177 @Override findLibrary(String name)178 public String findLibrary(String name) { 179 return pathList.findLibrary(name); 180 } 181 182 /** 183 * Returns package information for the given package. 184 * Unfortunately, instances of this class don't really have this 185 * information, and as a non-secure {@code ClassLoader}, it isn't 186 * even required to, according to the spec. Yet, we want to 187 * provide it, in order to make all those hopeful callers of 188 * {@code myClass.getPackage().getName()} happy. Thus we construct 189 * a {@code Package} object the first time it is being requested 190 * and fill most of the fields with dummy values. The {@code 191 * Package} object is then put into the {@code ClassLoader}'s 192 * package cache, so we see the same one next time. We don't 193 * create {@code Package} objects for {@code null} arguments or 194 * for the default package. 195 * 196 * <p>There is a limited chance that we end up with multiple 197 * {@code Package} objects representing the same package: It can 198 * happen when when a package is scattered across different JAR 199 * files which were loaded by different {@code ClassLoader} 200 * instances. This is rather unlikely, and given that this whole 201 * thing is more or less a workaround, probably not worth the 202 * effort to address. 203 * 204 * @param name the name of the class 205 * @return the package information for the class, or {@code null} 206 * if there is no package information available for it 207 */ 208 @Override getPackage(String name)209 protected synchronized Package getPackage(String name) { 210 if (name != null && !name.isEmpty()) { 211 Package pack = super.getPackage(name); 212 213 if (pack == null) { 214 pack = definePackage(name, "Unknown", "0.0", "Unknown", 215 "Unknown", "0.0", "Unknown", null); 216 } 217 218 return pack; 219 } 220 221 return null; 222 } 223 224 /** 225 * @hide 226 */ getLdLibraryPath()227 public String getLdLibraryPath() { 228 StringBuilder result = new StringBuilder(); 229 for (File directory : pathList.getNativeLibraryDirectories()) { 230 if (result.length() > 0) { 231 result.append(':'); 232 } 233 result.append(directory); 234 } 235 236 return result.toString(); 237 } 238 toString()239 @Override public String toString() { 240 return getClass().getName() + "[" + pathList + "]"; 241 } 242 243 /** 244 * Sets the reporter for dex load notifications. 245 * Once set, all new instances of BaseDexClassLoader will report upon 246 * constructions the loaded dex files. 247 * 248 * @param newReporter the new Reporter. Setting null will cancel reporting. 249 * @hide 250 */ setReporter(Reporter newReporter)251 public static void setReporter(Reporter newReporter) { 252 reporter = newReporter; 253 } 254 255 /** 256 * @hide 257 */ getReporter()258 public static Reporter getReporter() { 259 return reporter; 260 } 261 262 /** 263 * @hide 264 */ 265 public interface Reporter { 266 /** 267 * Reports the construction of a BaseDexClassLoader and provides information about the 268 * class loader chain. 269 * Note that this only reports if all class loader in the chain are BaseDexClassLoader. 270 * 271 * @param classLoadersChain the chain of class loaders used during the construction of the 272 * class loader. The first element is the BaseDexClassLoader being constructed, 273 * the second element is its parent, and so on. 274 * @param classPaths the class paths of the class loaders present in 275 * {@param classLoadersChain}. The first element corresponds to the first class 276 * loader and so on. A classpath is represented as a list of dex files separated by 277 * {@code File.pathSeparator}. 278 */ report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths)279 void report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths); 280 } 281 } 282