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 * Array of ClassLoaders identical to {@code sharedLibraryLoaders} except that these library 77 * loaders are always checked after the {@code pathList} when looking up classes and resources. 78 * 79 * The placement of a library into this group is done by the OEM and cannot be configured by 80 * an App. 81 * 82 * <p>{@code null} if the class loader has no shared library. 83 * 84 * @hide 85 */ 86 protected final ClassLoader[] sharedLibraryLoadersAfter; 87 88 /** 89 * Constructs an instance. 90 * Note that all the *.jar and *.apk files from {@code dexPath} might be 91 * first extracted in-memory before the code is loaded. This can be avoided 92 * by passing raw dex files (*.dex) in the {@code dexPath}. 93 * 94 * @param dexPath the list of jar/apk files containing classes and 95 * resources, delimited by {@code File.pathSeparator}, which 96 * defaults to {@code ":"} on Android. 97 * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26. 98 * @param librarySearchPath the list of directories containing native 99 * libraries, delimited by {@code File.pathSeparator}; may be 100 * {@code null} 101 * @param parent the parent class loader 102 */ BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)103 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 104 String librarySearchPath, ClassLoader parent) { 105 this(dexPath, librarySearchPath, parent, null, null, false); 106 } 107 108 /** 109 * @hide 110 */ 111 @UnsupportedAppUsage BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted)112 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 113 String librarySearchPath, ClassLoader parent, boolean isTrusted) { 114 this(dexPath, librarySearchPath, parent, null, null, isTrusted); 115 } 116 117 /** 118 * @hide 119 */ BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] libraries)120 public BaseDexClassLoader(String dexPath, 121 String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) { 122 this(dexPath, librarySearchPath, parent, libraries, null, false); 123 } 124 125 /** 126 * @hide 127 */ BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] libraries, ClassLoader[] librariesAfter)128 public BaseDexClassLoader(String dexPath, String librarySearchPath, 129 ClassLoader parent, ClassLoader[] libraries, ClassLoader[] librariesAfter) { 130 this(dexPath, librarySearchPath, parent, libraries, librariesAfter, false); 131 } 132 133 134 /** 135 * BaseDexClassLoader implements the Android 136 * <a href=https://developer.android.com/guide/topics/manifest/uses-library-element> 137 * shared libraries</a> feature by changing the typical parent delegation mechanism 138 * of class loaders. 139 * <p> Each shared library is associated with its own class loader, which is added to a list of 140 * class loaders this BaseDexClassLoader tries to load from in order, immediately checking 141 * after the parent. 142 * The shared library loaders are always checked before the {@code pathList} when looking 143 * up classes and resources. 144 * <p> 145 * The shared library loaders defined in sharedLibraryLoadersAfter are always checked 146 * <b>after</b> the {@code pathList} 147 * 148 * @hide 149 */ BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter, boolean isTrusted)150 public BaseDexClassLoader(String dexPath, 151 String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, 152 ClassLoader[] sharedLibraryLoadersAfter, 153 boolean isTrusted) { 154 super(parent); 155 // Setup shared libraries before creating the path list. ART relies on the class loader 156 // hierarchy being finalized before loading dex files. 157 this.sharedLibraryLoaders = sharedLibraryLoaders == null 158 ? null 159 : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); 160 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); 161 162 this.sharedLibraryLoadersAfter = sharedLibraryLoadersAfter == null 163 ? null 164 : Arrays.copyOf(sharedLibraryLoadersAfter, sharedLibraryLoadersAfter.length); 165 // Run background verification after having set 'pathList'. 166 this.pathList.maybeRunBackgroundVerification(this); 167 168 reportClassLoaderChain(); 169 } 170 171 /** 172 * Reports the current class loader chain to the registered {@code reporter}. 173 * 174 * @hide 175 */ 176 @SystemApi(client = MODULE_LIBRARIES) reportClassLoaderChain()177 public void reportClassLoaderChain() { 178 if (reporter == null) { 179 return; 180 } 181 182 String[] classPathAndClassLoaderContexts = computeClassLoaderContextsNative(); 183 if (classPathAndClassLoaderContexts.length == 0) { 184 return; 185 } 186 Map<String, String> dexFileMapping = 187 new HashMap<>(classPathAndClassLoaderContexts.length / 2); 188 for (int i = 0; i < classPathAndClassLoaderContexts.length; i += 2) { 189 dexFileMapping.put(classPathAndClassLoaderContexts[i], 190 classPathAndClassLoaderContexts[i + 1]); 191 } 192 reporter.report(Collections.unmodifiableMap(dexFileMapping)); 193 } 194 195 /** 196 * Computes the classloader contexts for each classpath entry in {@code pathList.getDexPaths()}. 197 * 198 * Note that this method is not thread safe, i.e. it is the responsibility of the caller to 199 * ensure that {@code pathList.getDexPaths()} is not modified concurrently with this method 200 * being called. 201 * 202 * @return A non-null array of non-null strings of length 203 * {@code 2 * pathList.getDexPaths().size()}. Every even index (0 is even here) is a dex file 204 * path and every odd entry is the class loader context used to load the previously listed dex 205 * file. E.g. a result might be {@code { "foo.dex", "PCL[]", "bar.dex", "PCL[foo.dex]" } }. 206 */ computeClassLoaderContextsNative()207 private native String[] computeClassLoaderContextsNative(); 208 209 /** 210 * Constructs an instance. 211 * 212 * dexFile must be an in-memory representation of a full dexFile. 213 * 214 * @param dexFiles the array of in-memory dex files containing classes. 215 * @param librarySearchPath the list of directories containing native 216 * libraries, delimited by {@code File.pathSeparator}; may be {@code null} 217 * @param parent the parent class loader 218 * 219 * @hide 220 */ BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent)221 public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) { 222 super(parent); 223 this.sharedLibraryLoaders = null; 224 this.sharedLibraryLoadersAfter = null; 225 this.pathList = new DexPathList(this, librarySearchPath); 226 this.pathList.initByteBufferDexPath(dexFiles); 227 // Run background verification after having set 'pathList'. 228 this.pathList.maybeRunBackgroundVerification(this); 229 } 230 231 @Override findClass(String name)232 protected Class<?> findClass(String name) throws ClassNotFoundException { 233 // First, check whether the class is present in our shared libraries. 234 if (sharedLibraryLoaders != null) { 235 for (ClassLoader loader : sharedLibraryLoaders) { 236 try { 237 return loader.loadClass(name); 238 } catch (ClassNotFoundException ignored) { 239 } 240 } 241 } 242 // Check whether the class in question is present in the dexPath that 243 // this classloader operates on. 244 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 245 Class c = pathList.findClass(name, suppressedExceptions); 246 if (c != null) { 247 return c; 248 } 249 // Now, check whether the class is present in the "after" shared libraries. 250 if (sharedLibraryLoadersAfter != null) { 251 for (ClassLoader loader : sharedLibraryLoadersAfter) { 252 try { 253 return loader.loadClass(name); 254 } catch (ClassNotFoundException ignored) { 255 } 256 } 257 } 258 if (c == null) { 259 ClassNotFoundException cnfe = new ClassNotFoundException( 260 "Didn't find class \"" + name + "\" on path: " + pathList); 261 for (Throwable t : suppressedExceptions) { 262 cnfe.addSuppressed(t); 263 } 264 throw cnfe; 265 } 266 return c; 267 } 268 269 /** 270 * Adds a new dex path to path list. 271 * 272 * @param dexPath dex path to add to path list 273 * 274 * @hide 275 */ 276 @UnsupportedAppUsage 277 @SystemApi(client = MODULE_LIBRARIES) addDexPath(@ullable String dexPath)278 public void addDexPath(@Nullable String dexPath) { 279 addDexPath(dexPath, false /*isTrusted*/); 280 } 281 282 /** 283 * @hide 284 */ 285 @UnsupportedAppUsage addDexPath(String dexPath, boolean isTrusted)286 public void addDexPath(String dexPath, boolean isTrusted) { 287 pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted); 288 } 289 290 /** 291 * Adds additional native paths for consideration in subsequent calls to 292 * {@link #findLibrary(String)}. 293 * 294 * @param libPaths collection of paths to be added to path list 295 * 296 * @hide 297 */ 298 @SystemApi(client = MODULE_LIBRARIES) addNativePath(@onNull Collection<String> libPaths)299 public void addNativePath(@NonNull Collection<String> libPaths) { 300 pathList.addNativePath(libPaths); 301 } 302 303 @Override findResource(String name)304 protected URL findResource(String name) { 305 if (sharedLibraryLoaders != null) { 306 for (ClassLoader loader : sharedLibraryLoaders) { 307 URL url = loader.getResource(name); 308 if (url != null) { 309 return url; 310 } 311 } 312 } 313 URL url = pathList.findResource(name); 314 if (url != null) { 315 return url; 316 } 317 if (sharedLibraryLoadersAfter != null) { 318 for (ClassLoader loader : sharedLibraryLoadersAfter) { 319 URL url2 = loader.getResource(name); 320 if (url2 != null) { 321 return url2; 322 } 323 } 324 } 325 return null; 326 } 327 328 @Override findResources(String name)329 protected Enumeration<URL> findResources(String name) { 330 Enumeration<URL> myResources = pathList.findResources(name); 331 if (sharedLibraryLoaders == null && sharedLibraryLoadersAfter == null) { 332 return myResources; 333 } 334 335 int sharedLibraryLoadersCount = 336 (sharedLibraryLoaders != null) ? sharedLibraryLoaders.length : 0; 337 int sharedLibraryLoadersAfterCount = 338 (sharedLibraryLoadersAfter != null) ? sharedLibraryLoadersAfter.length : 0; 339 340 Enumeration<URL>[] tmp = 341 (Enumeration<URL>[]) new Enumeration<?>[sharedLibraryLoadersCount + 342 sharedLibraryLoadersAfterCount 343 + 1]; 344 // First add sharedLibrary resources. 345 // This will add duplicate resources if a shared library is loaded twice, but that's ok 346 // as we don't guarantee uniqueness. 347 int i = 0; 348 for (; i < sharedLibraryLoadersCount; i++) { 349 try { 350 tmp[i] = sharedLibraryLoaders[i].getResources(name); 351 } catch (IOException e) { 352 // Ignore. 353 } 354 } 355 // Then add resource from this dex path. 356 tmp[i++] = myResources; 357 358 // Finally add resources from shared libraries that are to be loaded after. 359 for (int j = 0; j < sharedLibraryLoadersAfterCount; i++, j++) { 360 try { 361 tmp[i] = sharedLibraryLoadersAfter[j].getResources(name); 362 } catch (IOException e) { 363 // Ignore. 364 } 365 } 366 return new CompoundEnumeration<>(tmp); 367 } 368 369 @Override findLibrary(String name)370 public String findLibrary(String name) { 371 return pathList.findLibrary(name); 372 } 373 374 /** 375 * Returns package information for the given package. 376 * Unfortunately, instances of this class don't really have this 377 * information, and as a non-secure {@code ClassLoader}, it isn't 378 * even required to, according to the spec. Yet, we want to 379 * provide it, in order to make all those hopeful callers of 380 * {@code myClass.getPackage().getName()} happy. Thus we construct 381 * a {@code Package} object the first time it is being requested 382 * and fill most of the fields with fake values. The {@code 383 * Package} object is then put into the {@code ClassLoader}'s 384 * package cache, so we see the same one next time. We don't 385 * create {@code Package} objects for {@code null} arguments or 386 * for the default package. 387 * 388 * <p>There is a limited chance that we end up with multiple 389 * {@code Package} objects representing the same package: It can 390 * happen when when a package is scattered across different JAR 391 * files which were loaded by different {@code ClassLoader} 392 * instances. This is rather unlikely, and given that this whole 393 * thing is more or less a workaround, probably not worth the 394 * effort to address. 395 * 396 * @param name the name of the class 397 * @return the package information for the class, or {@code null} 398 * if there is no package information available for it 399 * 400 * @deprecated See {@link ClassLoader#getPackage(String)} 401 */ 402 @Deprecated 403 @Override getPackage(String name)404 protected synchronized Package getPackage(String name) { 405 if (name != null && !name.isEmpty()) { 406 Package pack = super.getPackage(name); 407 408 if (pack == null) { 409 pack = definePackage(name, "Unknown", "0.0", "Unknown", 410 "Unknown", "0.0", "Unknown", null); 411 } 412 413 return pack; 414 } 415 416 return null; 417 } 418 419 /** 420 * Returns colon-separated set of directories where libraries should be 421 * searched for first, before the standard set of directories. 422 * 423 * @return colon-separated set of search directories 424 * 425 * @hide 426 */ 427 @UnsupportedAppUsage 428 @SystemApi(client = MODULE_LIBRARIES) getLdLibraryPath()429 public @NonNull String getLdLibraryPath() { 430 StringBuilder result = new StringBuilder(); 431 for (File directory : pathList.getNativeLibraryDirectories()) { 432 if (result.length() > 0) { 433 result.append(':'); 434 } 435 result.append(directory); 436 } 437 438 return result.toString(); 439 } 440 toString()441 @Override public String toString() { 442 return getClass().getName() + "[" + pathList + "]"; 443 } 444 445 /** 446 * Sets the reporter for dex load notifications. 447 * Once set, all new instances of BaseDexClassLoader will report upon 448 * constructions the loaded dex files. 449 * 450 * @param newReporter the new Reporter. Setting {@code null} will cancel reporting. 451 * @hide 452 */ 453 @SystemApi(client = MODULE_LIBRARIES) setReporter(@ullable Reporter newReporter)454 public static void setReporter(@Nullable Reporter newReporter) { 455 reporter = newReporter; 456 } 457 458 /** 459 * @hide 460 */ getReporter()461 public static Reporter getReporter() { 462 return reporter; 463 } 464 465 /** 466 * Reports the construction of a {@link BaseDexClassLoader} and provides opaque 467 * information about the class loader chain. 468 * 469 * @hide 470 */ 471 @SystemApi(client = MODULE_LIBRARIES) 472 public interface Reporter { 473 /** 474 * Reports the construction of a BaseDexClassLoader and provides opaque information about 475 * the class loader chain. For example, if the childmost ClassLoader in the chain: 476 * {@quote BaseDexClassLoader { foo.dex } -> BaseDexClassLoader { base.apk } 477 * -> BootClassLoader } was just initialized then the load of {@code "foo.dex"} would be 478 * reported with a classLoaderContext of {@code "PCL[];PCL[base.apk]"}. 479 * 480 * @param contextsMap A map from dex file paths to the class loader context used to load 481 * each dex file. 482 * 483 * @hide 484 */ 485 @SystemApi(client = MODULE_LIBRARIES) report(@onNull Map<String, String> contextsMap)486 void report(@NonNull Map<String, String> contextsMap); 487 } 488 } 489