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 static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 21 import android.annotation.SystemApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import java.io.File; 24 import java.io.IOException; 25 import java.net.URL; 26 import java.nio.ByteBuffer; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collection; 30 import java.util.Collections; 31 import java.util.Enumeration; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 import libcore.util.NonNull; 36 import libcore.util.Nullable; 37 import sun.misc.CompoundEnumeration; 38 39 /** 40 * Base class for common functionality between various dex-based 41 * {@link ClassLoader} implementations. 42 */ 43 public class BaseDexClassLoader extends ClassLoader { 44 45 /** 46 * Hook for customizing how dex files loads are reported. 47 * 48 * This enables the framework to monitor the use of dex files. The 49 * goal is to simplify the mechanism for optimizing foreign dex files and 50 * enable further optimizations of secondary dex files. 51 * 52 * The reporting happens only when new instances of BaseDexClassLoader 53 * are constructed and will be active only after this field is set with 54 * {@link BaseDexClassLoader#setReporter}. 55 */ 56 /* @NonNull */ private static volatile Reporter reporter = null; 57 58 @UnsupportedAppUsage 59 private final DexPathList pathList; 60 61 /** 62 * Array of ClassLoaders that can be used to load classes and resources that the code in 63 * {@code pathList} may depend on. This is used to implement Android's 64 * <a href=https://developer.android.com/guide/topics/manifest/uses-library-element> 65 * shared libraries</a> feature. 66 * <p>The shared library loaders are always checked before the {@code pathList} when looking 67 * up classes and resources. 68 * 69 * <p>{@code null} if the class loader has no shared library. 70 * 71 * @hide 72 */ 73 protected final ClassLoader[] sharedLibraryLoaders; 74 75 /** 76 * Constructs an instance. 77 * Note that all the *.jar and *.apk files from {@code dexPath} might be 78 * first extracted in-memory before the code is loaded. This can be avoided 79 * by passing raw dex files (*.dex) in the {@code dexPath}. 80 * 81 * @param dexPath the list of jar/apk files containing classes and 82 * resources, delimited by {@code File.pathSeparator}, which 83 * defaults to {@code ":"} on Android. 84 * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26. 85 * @param librarySearchPath the list of directories containing native 86 * libraries, delimited by {@code File.pathSeparator}; may be 87 * {@code null} 88 * @param parent the parent class loader 89 */ BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)90 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 91 String librarySearchPath, ClassLoader parent) { 92 this(dexPath, librarySearchPath, parent, null, false); 93 } 94 95 /** 96 * @hide 97 */ 98 @UnsupportedAppUsage BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted)99 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 100 String librarySearchPath, ClassLoader parent, boolean isTrusted) { 101 this(dexPath, librarySearchPath, parent, null, isTrusted); 102 } 103 104 /** 105 * @hide 106 */ BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] libraries)107 public BaseDexClassLoader(String dexPath, 108 String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) { 109 this(dexPath, librarySearchPath, parent, libraries, false); 110 } 111 112 /** 113 * BaseDexClassLoader implements the Android 114 * <a href=https://developer.android.com/guide/topics/manifest/uses-library-element> 115 * shared libraries</a> feature by changing the typical parent delegation mechanism 116 * of class loaders. 117 * <p> Each shared library is associated with its own class loader, which is added to a list of 118 * class loaders this BaseDexClassLoader tries to load from in order, immediately checking 119 * after the parent. 120 * The shared library loaders are always checked before the {@code pathList} when looking 121 * up classes and resources. 122 * 123 * @hide 124 */ BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, boolean isTrusted)125 public BaseDexClassLoader(String dexPath, 126 String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, 127 boolean isTrusted) { 128 super(parent); 129 // Setup shared libraries before creating the path list. ART relies on the class loader 130 // hierarchy being finalized before loading dex files. 131 this.sharedLibraryLoaders = sharedLibraryLoaders == null 132 ? null 133 : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); 134 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); 135 136 // Run background verification after having set 'pathList'. 137 this.pathList.maybeRunBackgroundVerification(this); 138 139 reportClassLoaderChain(); 140 } 141 142 /** 143 * Reports the current class loader chain to the registered {@code reporter}. 144 * 145 * @hide 146 */ 147 @SystemApi(client = MODULE_LIBRARIES) 148 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) reportClassLoaderChain()149 public void reportClassLoaderChain() { 150 if (reporter == null) { 151 return; 152 } 153 154 String[] classPathAndClassLoaderContexts = computeClassLoaderContextsNative(); 155 if (classPathAndClassLoaderContexts.length == 0) { 156 return; 157 } 158 Map<String, String> dexFileMapping = 159 new HashMap<>(classPathAndClassLoaderContexts.length / 2); 160 for (int i = 0; i < classPathAndClassLoaderContexts.length; i += 2) { 161 dexFileMapping.put(classPathAndClassLoaderContexts[i], 162 classPathAndClassLoaderContexts[i + 1]); 163 } 164 reporter.report(Collections.unmodifiableMap(dexFileMapping)); 165 } 166 167 /** 168 * Computes the classloader contexts for each classpath entry in {@code pathList.getDexPaths()}. 169 * 170 * Note that this method is not thread safe, i.e. it is the responsibility of the caller to 171 * ensure that {@code pathList.getDexPaths()} is not modified concurrently with this method 172 * being called. 173 * 174 * @return A non-null array of non-null strings of length 175 * {@code 2 * pathList.getDexPaths().size()}. Every even index (0 is even here) is a dex file 176 * path and every odd entry is the class loader context used to load the previously listed dex 177 * file. E.g. a result might be {@code { "foo.dex", "PCL[]", "bar.dex", "PCL[foo.dex]" } }. 178 */ computeClassLoaderContextsNative()179 private native String[] computeClassLoaderContextsNative(); 180 181 /** 182 * Constructs an instance. 183 * 184 * dexFile must be an in-memory representation of a full dexFile. 185 * 186 * @param dexFiles the array of in-memory dex files containing classes. 187 * @param librarySearchPath the list of directories containing native 188 * libraries, delimited by {@code File.pathSeparator}; may be {@code null} 189 * @param parent the parent class loader 190 * 191 * @hide 192 */ BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent)193 public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) { 194 super(parent); 195 this.sharedLibraryLoaders = null; 196 this.pathList = new DexPathList(this, librarySearchPath); 197 this.pathList.initByteBufferDexPath(dexFiles); 198 // Run background verification after having set 'pathList'. 199 this.pathList.maybeRunBackgroundVerification(this); 200 } 201 202 @Override findClass(String name)203 protected Class<?> findClass(String name) throws ClassNotFoundException { 204 // First, check whether the class is present in our shared libraries. 205 if (sharedLibraryLoaders != null) { 206 for (ClassLoader loader : sharedLibraryLoaders) { 207 try { 208 return loader.loadClass(name); 209 } catch (ClassNotFoundException ignored) { 210 } 211 } 212 } 213 // Check whether the class in question is present in the dexPath that 214 // this classloader operates on. 215 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 216 Class c = pathList.findClass(name, suppressedExceptions); 217 if (c == null) { 218 ClassNotFoundException cnfe = new ClassNotFoundException( 219 "Didn't find class \"" + name + "\" on path: " + pathList); 220 for (Throwable t : suppressedExceptions) { 221 cnfe.addSuppressed(t); 222 } 223 throw cnfe; 224 } 225 return c; 226 } 227 228 /** 229 * Adds a new dex path to path list. 230 * 231 * @param dexPath dex path to add to path list 232 * 233 * @hide 234 */ 235 @UnsupportedAppUsage 236 @SystemApi(client = MODULE_LIBRARIES) 237 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) addDexPath(@ullable String dexPath)238 public void addDexPath(@Nullable String dexPath) { 239 addDexPath(dexPath, false /*isTrusted*/); 240 } 241 242 /** 243 * @hide 244 */ 245 @UnsupportedAppUsage addDexPath(String dexPath, boolean isTrusted)246 public void addDexPath(String dexPath, boolean isTrusted) { 247 pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted); 248 } 249 250 /** 251 * Adds additional native paths for consideration in subsequent calls to 252 * {@link #findLibrary(String)}. 253 * 254 * @param libPaths collection of paths to be added to path list 255 * 256 * @hide 257 */ 258 @SystemApi(client = MODULE_LIBRARIES) 259 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) addNativePath(@onNull Collection<String> libPaths)260 public void addNativePath(@NonNull Collection<String> libPaths) { 261 pathList.addNativePath(libPaths); 262 } 263 264 @Override findResource(String name)265 protected URL findResource(String name) { 266 if (sharedLibraryLoaders != null) { 267 for (ClassLoader loader : sharedLibraryLoaders) { 268 URL url = loader.getResource(name); 269 if (url != null) { 270 return url; 271 } 272 } 273 } 274 return pathList.findResource(name); 275 } 276 277 @Override findResources(String name)278 protected Enumeration<URL> findResources(String name) { 279 Enumeration<URL> myResources = pathList.findResources(name); 280 if (sharedLibraryLoaders == null) { 281 return myResources; 282 } 283 284 Enumeration<URL>[] tmp = 285 (Enumeration<URL>[]) new Enumeration<?>[sharedLibraryLoaders.length + 1]; 286 // This will add duplicate resources if a shared library is loaded twice, but that's ok 287 // as we don't guarantee uniqueness. 288 for (int i = 0; i < sharedLibraryLoaders.length; i++) { 289 try { 290 tmp[i] = sharedLibraryLoaders[i].getResources(name); 291 } catch (IOException e) { 292 // Ignore. 293 } 294 } 295 tmp[sharedLibraryLoaders.length] = myResources; 296 return new CompoundEnumeration<>(tmp); 297 } 298 299 @Override findLibrary(String name)300 public String findLibrary(String name) { 301 return pathList.findLibrary(name); 302 } 303 304 /** 305 * Returns package information for the given package. 306 * Unfortunately, instances of this class don't really have this 307 * information, and as a non-secure {@code ClassLoader}, it isn't 308 * even required to, according to the spec. Yet, we want to 309 * provide it, in order to make all those hopeful callers of 310 * {@code myClass.getPackage().getName()} happy. Thus we construct 311 * a {@code Package} object the first time it is being requested 312 * and fill most of the fields with fake values. The {@code 313 * Package} object is then put into the {@code ClassLoader}'s 314 * package cache, so we see the same one next time. We don't 315 * create {@code Package} objects for {@code null} arguments or 316 * for the default package. 317 * 318 * <p>There is a limited chance that we end up with multiple 319 * {@code Package} objects representing the same package: It can 320 * happen when when a package is scattered across different JAR 321 * files which were loaded by different {@code ClassLoader} 322 * instances. This is rather unlikely, and given that this whole 323 * thing is more or less a workaround, probably not worth the 324 * effort to address. 325 * 326 * @param name the name of the class 327 * @return the package information for the class, or {@code null} 328 * if there is no package information available for it 329 */ 330 @Override getPackage(String name)331 protected synchronized Package getPackage(String name) { 332 if (name != null && !name.isEmpty()) { 333 Package pack = super.getPackage(name); 334 335 if (pack == null) { 336 pack = definePackage(name, "Unknown", "0.0", "Unknown", 337 "Unknown", "0.0", "Unknown", null); 338 } 339 340 return pack; 341 } 342 343 return null; 344 } 345 346 /** 347 * Returns colon-separated set of directories where libraries should be 348 * searched for first, before the standard set of directories. 349 * 350 * @return colon-separated set of search directories 351 * 352 * @hide 353 */ 354 @UnsupportedAppUsage 355 @SystemApi(client = MODULE_LIBRARIES) 356 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) getLdLibraryPath()357 public @NonNull String getLdLibraryPath() { 358 StringBuilder result = new StringBuilder(); 359 for (File directory : pathList.getNativeLibraryDirectories()) { 360 if (result.length() > 0) { 361 result.append(':'); 362 } 363 result.append(directory); 364 } 365 366 return result.toString(); 367 } 368 toString()369 @Override public String toString() { 370 return getClass().getName() + "[" + pathList + "]"; 371 } 372 373 /** 374 * Sets the reporter for dex load notifications. 375 * Once set, all new instances of BaseDexClassLoader will report upon 376 * constructions the loaded dex files. 377 * 378 * @param newReporter the new Reporter. Setting {@code null} will cancel reporting. 379 * @hide 380 */ 381 @SystemApi(client = MODULE_LIBRARIES) 382 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) setReporter(@ullable Reporter newReporter)383 public static void setReporter(@Nullable Reporter newReporter) { 384 reporter = newReporter; 385 } 386 387 /** 388 * @hide 389 */ getReporter()390 public static Reporter getReporter() { 391 return reporter; 392 } 393 394 /** 395 * Reports the construction of a {@link BaseDexClassLoader} and provides opaque 396 * information about the class loader chain. 397 * 398 * @hide 399 */ 400 @SystemApi(client = MODULE_LIBRARIES) 401 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 402 public interface Reporter { 403 /** 404 * Reports the construction of a BaseDexClassLoader and provides opaque information about 405 * the class loader chain. For example, if the childmost ClassLoader in the chain: 406 * {@quote BaseDexClassLoader { foo.dex } -> BaseDexClassLoader { base.apk } 407 * -> BootClassLoader } was just initialized then the load of {@code "foo.dex"} would be 408 * reported with a classLoaderContext of {@code "PCL[];PCL[base.apk]"}. 409 * 410 * @param contextsMap A map from dex file paths to the class loader context used to load 411 * each dex file. 412 * 413 * @hide 414 */ 415 @SystemApi(client = MODULE_LIBRARIES) 416 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) report(@onNull Map<String, String> contextsMap)417 void report(@NonNull Map<String, String> contextsMap); 418 } 419 } 420