1 /* 2 * Copyright (C) 2016 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 com.android.server.pm.dex; 18 19 import android.os.Build; 20 import android.util.AtomicFile; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.GuardedBy; 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.FastPrintWriter; 26 import com.android.server.pm.AbstractStatsBase; 27 import com.android.server.pm.PackageManagerServiceUtils; 28 29 import dalvik.system.VMRuntime; 30 31 import libcore.io.IoUtils; 32 33 import java.io.BufferedReader; 34 import java.io.FileNotFoundException; 35 import java.io.FileOutputStream; 36 import java.io.IOException; 37 import java.io.InputStreamReader; 38 import java.io.OutputStreamWriter; 39 import java.io.Reader; 40 import java.io.StringWriter; 41 import java.io.Writer; 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Objects; 50 import java.util.Set; 51 52 /** 53 * Stat file which store usage information about dex files. 54 */ 55 public class PackageDexUsage extends AbstractStatsBase<Void> { 56 private static final String TAG = "PackageDexUsage"; 57 58 // We are currently at version 2. 59 // Version 1 was introduced in Nougat and Version 2 in Oreo. 60 // We dropped version 1 support in R since all devices should have updated 61 // already. 62 private static final int PACKAGE_DEX_USAGE_VERSION = 2; 63 64 private static final String PACKAGE_DEX_USAGE_VERSION_HEADER = 65 "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; 66 67 private static final String SPLIT_CHAR = ","; 68 private static final String CODE_PATH_LINE_CHAR = "+"; 69 private static final String DEX_LINE_CHAR = "#"; 70 private static final String LOADING_PACKAGE_CHAR = "@"; 71 72 // One of the things we record about dex files is the class loader context that was used to 73 // load them. That should be stable but if it changes we don't keep track of variable contexts. 74 // Instead we put a special marker in the dex usage file in order to recognize the case and 75 // skip optimizations on that dex files. 76 /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = 77 "=VariableClassLoaderContext="; 78 79 // The marker used for unsupported class loader contexts (no longer written, may occur in old 80 // files so discarded on read). Note: this matches 81 // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime. 82 /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = 83 "=UnsupportedClassLoaderContext="; 84 85 /** 86 * Limit on how many secondary DEX paths we store for a single owner, to avoid one app causing 87 * unbounded memory consumption. 88 */ 89 @VisibleForTesting 90 /* package */ static final int MAX_SECONDARY_FILES_PER_OWNER = 100; 91 92 // Map which structures the information we have on a package. 93 // Maps package name to package data (which stores info about UsedByOtherApps and 94 // secondary dex files.). 95 // Access to this map needs synchronized. 96 @GuardedBy("mPackageUseInfoMap") 97 private final Map<String, PackageUseInfo> mPackageUseInfoMap; 98 PackageDexUsage()99 /* package */ PackageDexUsage() { 100 super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); 101 mPackageUseInfoMap = new HashMap<>(); 102 } 103 104 /** 105 * Record a dex file load. 106 * 107 * Note this is called when apps load dex files and as such it should return 108 * as fast as possible. 109 * 110 * @param owningPackageName the package owning the dex path 111 * @param dexPath the path of the dex files being loaded 112 * @param ownerUserId the user id which runs the code loading the dex files 113 * @param loaderIsa the ISA of the app loading the dex files 114 * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates 115 * the file is either primary or a split. False indicates the file is secondary dex. 116 * @param loadingPackageName the package performing the load. Recorded only if it is different 117 * than {@param owningPackageName}. 118 * @param overwriteCLC if true, the class loader context will be overwritten instead of being 119 * merged 120 * @return true if the dex load constitutes new information, or false if this information 121 * has been seen before. 122 */ record(String owningPackageName, String dexPath, int ownerUserId, String loaderIsa, boolean primaryOrSplit, String loadingPackageName, String classLoaderContext, boolean overwriteCLC)123 /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, 124 String loaderIsa, boolean primaryOrSplit, 125 String loadingPackageName, String classLoaderContext, boolean overwriteCLC) { 126 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 127 throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); 128 } 129 if (classLoaderContext == null) { 130 throw new IllegalArgumentException("Null classLoaderContext"); 131 } 132 if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { 133 Slog.e(TAG, "Unsupported context?"); 134 return false; 135 } 136 137 boolean isUsedByOtherApps = !owningPackageName.equals(loadingPackageName); 138 139 synchronized (mPackageUseInfoMap) { 140 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); 141 if (packageUseInfo == null) { 142 // This is the first time we see the package. 143 packageUseInfo = new PackageUseInfo(owningPackageName); 144 if (primaryOrSplit) { 145 // If we have a primary or a split apk, set isUsedByOtherApps. 146 // We do not need to record the loaderIsa or the owner because we compile 147 // primaries for all users and all ISAs. 148 packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); 149 } else { 150 // For secondary dex files record the loaderISA and the owner. We'll need 151 // to know under which user to compile and for what ISA. 152 DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, 153 classLoaderContext, loaderIsa); 154 packageUseInfo.mDexUseInfoMap.put(dexPath, newData); 155 maybeAddLoadingPackage(owningPackageName, loadingPackageName, 156 newData.mLoadingPackages); 157 } 158 mPackageUseInfoMap.put(owningPackageName, packageUseInfo); 159 return true; 160 } else { 161 // We already have data on this package. Amend it. 162 if (primaryOrSplit) { 163 // We have a possible update on the primary apk usage. Merge 164 // dex path information and return if there was an update. 165 return packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); 166 } else { 167 DexUseInfo newData = new DexUseInfo( 168 isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); 169 boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, 170 loadingPackageName, newData.mLoadingPackages); 171 172 DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); 173 if (existingData == null) { 174 // It's the first time we see this dex file. 175 if (packageUseInfo.mDexUseInfoMap.size() < MAX_SECONDARY_FILES_PER_OWNER) { 176 packageUseInfo.mDexUseInfoMap.put(dexPath, newData); 177 return true; 178 } else { 179 return updateLoadingPackages; 180 } 181 } else { 182 if (ownerUserId != existingData.mOwnerUserId) { 183 // Oups, this should never happen, the DexManager who calls this should 184 // do the proper checks and not call record if the user does not own the 185 // dex path. 186 // Secondary dex files are stored in the app user directory. A change in 187 // owningUser for the same path means that something went wrong at some 188 // higher level, and the loaderUser was allowed to cross 189 // user-boundaries and access data from what we know to be the owner 190 // user. 191 throw new IllegalArgumentException("Trying to change ownerUserId for " 192 + " dex path " + dexPath + " from " + existingData.mOwnerUserId 193 + " to " + ownerUserId); 194 } 195 // Merge the information into the existing data. 196 // Returns true if there was an update. 197 return existingData.merge(newData, overwriteCLC) || updateLoadingPackages; 198 } 199 } 200 } 201 } 202 } 203 204 /** 205 * Convenience method for sync reads which does not force the user to pass a useless 206 * (Void) null. 207 */ read()208 /* package */ void read() { 209 read((Void) null); 210 } 211 212 /** 213 * Convenience method for async writes which does not force the user to pass a useless 214 * (Void) null. 215 */ maybeWriteAsync()216 /*package*/ void maybeWriteAsync() { 217 maybeWriteAsync(null); 218 } 219 writeNow()220 /*package*/ void writeNow() { 221 writeInternal(null); 222 } 223 224 @Override writeInternal(Void data)225 protected void writeInternal(Void data) { 226 AtomicFile file = getFile(); 227 FileOutputStream f = null; 228 229 try { 230 f = file.startWrite(); 231 OutputStreamWriter osw = new OutputStreamWriter(f); 232 write(osw); 233 osw.flush(); 234 file.finishWrite(f); 235 } catch (IOException e) { 236 if (f != null) { 237 file.failWrite(f); 238 } 239 Slog.e(TAG, "Failed to write usage for dex files", e); 240 } 241 } 242 243 /** 244 * File format: 245 * 246 * file_magic_version 247 * package_name_1 248 * +code_path1 249 * @ loading_package_1_1, loading_package_1_2... 250 * +code_path2 251 * @ loading_package_2_1, loading_package_2_2... 252 * #dex_file_path_1_1 253 * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 254 * @ loading_package_1_1_1, loading_package_1_1_2... 255 * class_loader_context_1_1 256 * #dex_file_path_1_2 257 * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 258 * @ loading_package_1_2_1, loading_package_1_2_2... 259 * class_loader_context_1_2 260 * ... 261 */ write(Writer out)262 /* package */ void write(Writer out) { 263 // Make a clone to avoid locking while writing to disk. 264 Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap(); 265 266 FastPrintWriter fpw = new FastPrintWriter(out); 267 268 // Write the header. 269 fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER); 270 fpw.println(PACKAGE_DEX_USAGE_VERSION); 271 272 for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) { 273 // Write the package line. 274 String packageName = pEntry.getKey(); 275 PackageUseInfo packageUseInfo = pEntry.getValue(); 276 fpw.println(packageName); 277 278 // Write the code paths used by other apps. 279 for (Map.Entry<String, Set<String>> codeEntry : 280 packageUseInfo.mPrimaryCodePaths.entrySet()) { 281 String codePath = codeEntry.getKey(); 282 Set<String> loadingPackages = codeEntry.getValue(); 283 fpw.println(CODE_PATH_LINE_CHAR + codePath); 284 fpw.println(LOADING_PACKAGE_CHAR + String.join(SPLIT_CHAR, loadingPackages)); 285 } 286 287 // Write dex file lines. 288 for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { 289 String dexPath = dEntry.getKey(); 290 DexUseInfo dexUseInfo = dEntry.getValue(); 291 fpw.println(DEX_LINE_CHAR + dexPath); 292 fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), 293 writeBoolean(dexUseInfo.mIsUsedByOtherApps))); 294 for (String isa : dexUseInfo.mLoaderIsas) { 295 fpw.print(SPLIT_CHAR + isa); 296 } 297 fpw.println(); 298 fpw.println(LOADING_PACKAGE_CHAR 299 + String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); 300 fpw.println(dexUseInfo.getClassLoaderContext()); 301 } 302 } 303 fpw.flush(); 304 } 305 306 @Override readInternal(Void data)307 protected void readInternal(Void data) { 308 AtomicFile file = getFile(); 309 BufferedReader in = null; 310 try { 311 in = new BufferedReader(new InputStreamReader(file.openRead())); 312 read(in); 313 } catch (FileNotFoundException expected) { 314 // The file may not be there. E.g. When we first take the OTA with this feature. 315 } catch (IOException e) { 316 Slog.w(TAG, "Failed to parse package dex usage.", e); 317 } finally { 318 IoUtils.closeQuietly(in); 319 } 320 } 321 read(Reader reader)322 /* package */ void read(Reader reader) throws IOException { 323 Map<String, PackageUseInfo> data = new HashMap<>(); 324 BufferedReader in = new BufferedReader(reader); 325 // Read header, do version check. 326 String versionLine = in.readLine(); 327 int version; 328 if (versionLine == null) { 329 throw new IllegalStateException("No version line found."); 330 } else { 331 if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) { 332 // TODO(calin): the caller is responsible to clear the file. 333 throw new IllegalStateException("Invalid version line: " + versionLine); 334 } 335 version = Integer.parseInt( 336 versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); 337 if (!isSupportedVersion(version)) { 338 Slog.w(TAG, "Unexpected package-dex-use version: " + version 339 + ". Not reading from it"); 340 return; 341 } 342 } 343 344 String line; 345 String currentPackage = null; 346 PackageUseInfo currentPackageData = null; 347 348 Set<String> supportedIsas = new HashSet<>(); 349 for (String abi : Build.SUPPORTED_ABIS) { 350 supportedIsas.add(VMRuntime.getInstructionSet(abi)); 351 } 352 while ((line = in.readLine()) != null) { 353 if (line.startsWith(DEX_LINE_CHAR)) { 354 // This is the start of the the dex lines. 355 // We expect 4 lines for each dex entry: 356 // #dexPaths 357 // @loading_package_1,loading_package_2,... 358 // class_loader_context 359 // onwerUserId,isUsedByOtherApps,isa1,isa2 360 if (currentPackage == null) { 361 throw new IllegalStateException( 362 "Malformed PackageDexUsage file. Expected package line before dex line."); 363 } 364 365 // Line 1 is the dex path. 366 String dexPath = line.substring(DEX_LINE_CHAR.length()); 367 368 // Line 2 is the dex data: (userId, isUsedByOtherApps, isa). 369 line = in.readLine(); 370 if (line == null) { 371 throw new IllegalStateException("Could not find dexUseInfo line"); 372 } 373 String[] elems = line.split(SPLIT_CHAR); 374 if (elems.length < 3) { 375 throw new IllegalStateException("Invalid PackageDexUsage line: " + line); 376 } 377 378 Set<String> loadingPackages = readLoadingPackages(in, version); 379 String classLoaderContext = readClassLoaderContext(in, version); 380 381 if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(classLoaderContext)) { 382 // We used to record use of unsupported class loaders, but we no longer do. 383 // Discard such entries; they will be deleted when we next write the file. 384 continue; 385 } 386 387 int ownerUserId = Integer.parseInt(elems[0]); 388 boolean isUsedByOtherApps = readBoolean(elems[1]); 389 DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, 390 classLoaderContext, /*isa*/ null); 391 dexUseInfo.mLoadingPackages.addAll(loadingPackages); 392 for (int i = 2; i < elems.length; i++) { 393 String isa = elems[i]; 394 if (supportedIsas.contains(isa)) { 395 dexUseInfo.mLoaderIsas.add(elems[i]); 396 } else { 397 // Should never happen unless someone crafts the file manually. 398 // In theory it could if we drop a supported ISA after an OTA but we don't 399 // do that. 400 Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa); 401 } 402 } 403 if (supportedIsas.isEmpty()) { 404 Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " + 405 "unsupported isas. dexPath=" + dexPath); 406 continue; 407 } 408 currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); 409 } else if (line.startsWith(CODE_PATH_LINE_CHAR)) { 410 // Expects 2 lines: 411 // +code_paths 412 // @loading_packages 413 String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); 414 Set<String> loadingPackages = readLoadingPackages(in, version); 415 currentPackageData.mPrimaryCodePaths.put(codePath, loadingPackages); 416 } else { 417 // This is a package line. 418 currentPackage = line; 419 currentPackageData = new PackageUseInfo(currentPackage); 420 data.put(currentPackage, currentPackageData); 421 } 422 } 423 424 synchronized (mPackageUseInfoMap) { 425 mPackageUseInfoMap.clear(); 426 mPackageUseInfoMap.putAll(data); 427 } 428 } 429 430 /** 431 * Reads the class loader context encoding from the buffer {@code in}. 432 */ readClassLoaderContext(BufferedReader in, int version)433 private String readClassLoaderContext(BufferedReader in, int version) throws IOException { 434 String context = in.readLine(); 435 if (context == null) { 436 throw new IllegalStateException("Could not find the classLoaderContext line."); 437 } 438 return context; 439 } 440 441 /** 442 * Reads the list of loading packages from the buffer {@code in}. 443 */ readLoadingPackages(BufferedReader in, int version)444 private Set<String> readLoadingPackages(BufferedReader in, int version) 445 throws IOException { 446 String line = in.readLine(); 447 if (line == null) { 448 throw new IllegalStateException("Could not find the loadingPackages line."); 449 } 450 Set<String> result = new HashSet<>(); 451 if (line.length() != LOADING_PACKAGE_CHAR.length()) { 452 Collections.addAll(result, 453 line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); 454 } 455 return result; 456 } 457 458 /** 459 * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's 460 * not equal to {@param owningPackage} 461 */ maybeAddLoadingPackage(String owningPackage, String loadingPackage, Set<String> loadingPackages)462 private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, 463 Set<String> loadingPackages) { 464 return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); 465 } 466 isSupportedVersion(int version)467 private boolean isSupportedVersion(int version) { 468 return version == PACKAGE_DEX_USAGE_VERSION; 469 } 470 471 /** 472 * Syncs the existing data with the set of available packages by removing obsolete entries. 473 */ syncData(Map<String, Set<Integer>> packageToUsersMap, Map<String, Set<String>> packageToCodePaths, List<String> packagesToKeepDataAbout)474 /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, 475 Map<String, Set<String>> packageToCodePaths, 476 List<String> packagesToKeepDataAbout) { 477 synchronized (mPackageUseInfoMap) { 478 Iterator<Map.Entry<String, PackageUseInfo>> pIt = 479 mPackageUseInfoMap.entrySet().iterator(); 480 while (pIt.hasNext()) { 481 Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); 482 String packageName = pEntry.getKey(); 483 if (packagesToKeepDataAbout.contains(packageName)) { 484 // This is a package for which we should keep the data even if it's not 485 // in the list of user packages. 486 continue; 487 } 488 PackageUseInfo packageUseInfo = pEntry.getValue(); 489 Set<Integer> users = packageToUsersMap.get(packageName); 490 if (users == null) { 491 // The package doesn't exist anymore, remove the record. 492 pIt.remove(); 493 } else { 494 // The package exists but we can prune the entries associated with non existing 495 // users. 496 Iterator<Map.Entry<String, DexUseInfo>> dIt = 497 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 498 while (dIt.hasNext()) { 499 DexUseInfo dexUseInfo = dIt.next().getValue(); 500 if (!users.contains(dexUseInfo.mOwnerUserId)) { 501 // User was probably removed. Delete its dex usage info. 502 dIt.remove(); 503 } 504 } 505 506 // Sync the code paths. 507 Set<String> codePaths = packageToCodePaths.get(packageName); 508 509 Iterator<Map.Entry<String, Set<String>>> recordedIt = 510 packageUseInfo.mPrimaryCodePaths.entrySet().iterator(); 511 while (recordedIt.hasNext()) { 512 Map.Entry<String, Set<String>> entry = recordedIt.next(); 513 String recordedCodePath = entry.getKey(); 514 if (!codePaths.contains(recordedCodePath)) { 515 // Clean up a non existing code path. 516 recordedIt.remove(); 517 } else { 518 // Clean up a non existing loading package. 519 Set<String> recordedLoadingPackages = entry.getValue(); 520 Iterator<String> recordedLoadingPackagesIt = 521 recordedLoadingPackages.iterator(); 522 while (recordedLoadingPackagesIt.hasNext()) { 523 String recordedLoadingPackage = recordedLoadingPackagesIt.next(); 524 if (!packagesToKeepDataAbout.contains(recordedLoadingPackage) 525 && !packageToUsersMap.containsKey(recordedLoadingPackage)) { 526 recordedLoadingPackagesIt.remove(); 527 } 528 } 529 } 530 } 531 532 if (!packageUseInfo.isAnyCodePathUsedByOtherApps() 533 && packageUseInfo.mDexUseInfoMap.isEmpty()) { 534 // The package is not used by other apps and we removed all its dex files 535 // records. Remove the entire package record as well. 536 pIt.remove(); 537 } 538 } 539 } 540 } 541 } 542 543 /** 544 * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. 545 * @return true if the package usage info was updated. 546 */ clearUsedByOtherApps(String packageName)547 /*package*/ boolean clearUsedByOtherApps(String packageName) { 548 synchronized (mPackageUseInfoMap) { 549 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 550 if (packageUseInfo == null) { 551 return false; 552 } 553 return packageUseInfo.clearCodePathUsedByOtherApps(); 554 } 555 } 556 557 /** 558 * Remove the usage data associated with package {@code packageName}. 559 * @return true if the package usage was found and removed successfully. 560 */ removePackage(String packageName)561 /* package */ boolean removePackage(String packageName) { 562 synchronized (mPackageUseInfoMap) { 563 return mPackageUseInfoMap.remove(packageName) != null; 564 } 565 } 566 567 /** 568 * Remove all the records about package {@code packageName} belonging to user {@code userId}. 569 * If the package is left with no records of secondary dex usage and is not used by other 570 * apps it will be removed as well. 571 * @return true if the record was found and actually deleted, 572 * false if the record doesn't exist 573 */ removeUserPackage(String packageName, int userId)574 /*package*/ boolean removeUserPackage(String packageName, int userId) { 575 synchronized (mPackageUseInfoMap) { 576 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 577 if (packageUseInfo == null) { 578 return false; 579 } 580 boolean updated = false; 581 Iterator<Map.Entry<String, DexUseInfo>> dIt = 582 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 583 while (dIt.hasNext()) { 584 DexUseInfo dexUseInfo = dIt.next().getValue(); 585 if (dexUseInfo.mOwnerUserId == userId) { 586 dIt.remove(); 587 updated = true; 588 } 589 } 590 // If no secondary dex info is left and the package is not used by other apps 591 // remove the data since it is now useless. 592 if (packageUseInfo.mDexUseInfoMap.isEmpty() 593 && !packageUseInfo.isAnyCodePathUsedByOtherApps()) { 594 mPackageUseInfoMap.remove(packageName); 595 updated = true; 596 } 597 return updated; 598 } 599 } 600 601 /** 602 * Remove the secondary dex file record belonging to the package {@code packageName} 603 * and user {@code userId}. 604 * @return true if the record was found and actually deleted, 605 * false if the record doesn't exist 606 */ removeDexFile(String packageName, String dexFile, int userId)607 /*package*/ boolean removeDexFile(String packageName, String dexFile, int userId) { 608 synchronized (mPackageUseInfoMap) { 609 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 610 if (packageUseInfo == null) { 611 return false; 612 } 613 return removeDexFile(packageUseInfo, dexFile, userId); 614 } 615 } 616 removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId)617 private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { 618 DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); 619 if (dexUseInfo == null) { 620 return false; 621 } 622 if (dexUseInfo.mOwnerUserId == userId) { 623 packageUseInfo.mDexUseInfoMap.remove(dexFile); 624 return true; 625 } 626 return false; 627 } 628 getPackageUseInfo(String packageName)629 /*package*/ PackageUseInfo getPackageUseInfo(String packageName) { 630 synchronized (mPackageUseInfoMap) { 631 PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); 632 // The useInfo contains a map for secondary dex files which could be modified 633 // concurrently after this method returns and thus outside the locking we do here. 634 // (i.e. the map is updated when new class loaders are created, which can happen anytime 635 // after this method returns) 636 // Make a defensive copy to be sure we don't get concurrent modifications. 637 return useInfo == null ? null : new PackageUseInfo(useInfo); 638 } 639 } 640 641 /** 642 * Return all packages that contain records of secondary dex files. 643 */ getAllPackagesWithSecondaryDexFiles()644 /*package*/ Set<String> getAllPackagesWithSecondaryDexFiles() { 645 Set<String> packages = new HashSet<>(); 646 synchronized (mPackageUseInfoMap) { 647 for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { 648 if (!entry.getValue().mDexUseInfoMap.isEmpty()) { 649 packages.add(entry.getKey()); 650 } 651 } 652 } 653 return packages; 654 } 655 clear()656 /* package */ void clear() { 657 synchronized (mPackageUseInfoMap) { 658 mPackageUseInfoMap.clear(); 659 } 660 } 661 662 // Creates a deep copy of the class' mPackageUseInfoMap. clonePackageUseInfoMap()663 private Map<String, PackageUseInfo> clonePackageUseInfoMap() { 664 Map<String, PackageUseInfo> clone = new HashMap<>(); 665 synchronized (mPackageUseInfoMap) { 666 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { 667 clone.put(e.getKey(), new PackageUseInfo(e.getValue())); 668 } 669 } 670 return clone; 671 } 672 writeBoolean(boolean bool)673 private String writeBoolean(boolean bool) { 674 return bool ? "1" : "0"; 675 } 676 readBoolean(String bool)677 private boolean readBoolean(String bool) { 678 if ("0".equals(bool)) return false; 679 if ("1".equals(bool)) return true; 680 throw new IllegalArgumentException("Unknown bool encoding: " + bool); 681 } 682 dump()683 /* package */ String dump() { 684 StringWriter sw = new StringWriter(); 685 write(sw); 686 return sw.toString(); 687 } 688 689 /** 690 * Stores data on how a package and its dex files are used. 691 */ 692 public static class PackageUseInfo { 693 // The name of the package this info belongs to. 694 private final String mPackageName; 695 // The app's code paths that are used by other apps. 696 // The key is the code path and the value is the set of loading packages. 697 private final Map<String, Set<String>> mPrimaryCodePaths; 698 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). 699 private final Map<String, DexUseInfo> mDexUseInfoMap; 700 PackageUseInfo(String packageName)701 /*package*/ PackageUseInfo(String packageName) { 702 mPrimaryCodePaths = new HashMap<>(); 703 mDexUseInfoMap = new HashMap<>(); 704 mPackageName = packageName; 705 } 706 707 // Creates a deep copy of the `other`. PackageUseInfo(PackageUseInfo other)708 private PackageUseInfo(PackageUseInfo other) { 709 mPackageName = other.mPackageName; 710 mPrimaryCodePaths = new HashMap<>(); 711 for (Map.Entry<String, Set<String>> e : other.mPrimaryCodePaths.entrySet()) { 712 mPrimaryCodePaths.put(e.getKey(), new HashSet<>(e.getValue())); 713 } 714 715 mDexUseInfoMap = new HashMap<>(); 716 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { 717 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); 718 } 719 } 720 mergePrimaryCodePaths(String codePath, String loadingPackage)721 private boolean mergePrimaryCodePaths(String codePath, String loadingPackage) { 722 Set<String> loadingPackages = mPrimaryCodePaths.get(codePath); 723 if (loadingPackages == null) { 724 loadingPackages = new HashSet<>(); 725 mPrimaryCodePaths.put(codePath, loadingPackages); 726 } 727 return loadingPackages.add(loadingPackage); 728 } 729 isUsedByOtherApps(String codePath)730 public boolean isUsedByOtherApps(String codePath) { 731 if (mPrimaryCodePaths.containsKey(codePath)) { 732 Set<String> loadingPackages = mPrimaryCodePaths.get(codePath); 733 if (loadingPackages.contains(mPackageName)) { 734 // If the owning package is in the list then this code path 735 // is used by others if there are other packages in the list. 736 return loadingPackages.size() > 1; 737 } else { 738 // The owning package is not in the loading packages. So if 739 // the list is non-empty then the code path is used by others. 740 return !loadingPackages.isEmpty(); 741 } 742 } 743 return false; 744 } 745 getDexUseInfoMap()746 public Map<String, DexUseInfo> getDexUseInfoMap() { 747 return mDexUseInfoMap; 748 } 749 getLoadingPackages(String codePath)750 public Set<String> getLoadingPackages(String codePath) { 751 return mPrimaryCodePaths.getOrDefault(codePath, null); 752 } 753 isAnyCodePathUsedByOtherApps()754 public boolean isAnyCodePathUsedByOtherApps() { 755 return !mPrimaryCodePaths.isEmpty(); 756 } 757 758 /** 759 * Clears the usedByOtherApps markers from all code paths. 760 * Returns whether or not there was an update. 761 */ clearCodePathUsedByOtherApps()762 /*package*/ boolean clearCodePathUsedByOtherApps() { 763 boolean updated = false; 764 List<String> retainOnlyOwningPackage = new ArrayList<>(1); 765 retainOnlyOwningPackage.add(mPackageName); 766 for (Map.Entry<String, Set<String>> entry : mPrimaryCodePaths.entrySet()) { 767 // Remove or loading packages but the owning one. 768 if (entry.getValue().retainAll(retainOnlyOwningPackage)) { 769 updated = true; 770 } 771 } 772 return updated; 773 } 774 } 775 776 /** 777 * Stores data about a loaded dex files. 778 */ 779 public static class DexUseInfo { 780 private boolean mIsUsedByOtherApps; 781 private final int mOwnerUserId; 782 // The class loader context for the dex file. This encodes the class loader chain 783 // (class loader type + class path) in a format compatible to dex2oat. 784 // See {@code DexoptUtils.processContextForDexLoad}. 785 private String mClassLoaderContext; 786 // The instructions sets of the applications loading the dex file. 787 private final Set<String> mLoaderIsas; 788 // Packages who load this dex file. 789 private final Set<String> mLoadingPackages; 790 791 @VisibleForTesting DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, String loaderIsa)792 /* package */ DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, 793 String classLoaderContext, String loaderIsa) { 794 mIsUsedByOtherApps = isUsedByOtherApps; 795 mOwnerUserId = ownerUserId; 796 mClassLoaderContext = classLoaderContext; 797 mLoaderIsas = new HashSet<>(); 798 if (loaderIsa != null) { 799 mLoaderIsas.add(loaderIsa); 800 } 801 mLoadingPackages = new HashSet<>(); 802 } 803 804 // Creates a deep copy of the `other`. DexUseInfo(DexUseInfo other)805 private DexUseInfo(DexUseInfo other) { 806 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 807 mOwnerUserId = other.mOwnerUserId; 808 mClassLoaderContext = other.mClassLoaderContext; 809 mLoaderIsas = new HashSet<>(other.mLoaderIsas); 810 mLoadingPackages = new HashSet<>(other.mLoadingPackages); 811 } 812 merge(DexUseInfo dexUseInfo, boolean overwriteCLC)813 private boolean merge(DexUseInfo dexUseInfo, boolean overwriteCLC) { 814 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 815 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; 816 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); 817 boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); 818 819 String oldClassLoaderContext = mClassLoaderContext; 820 if (overwriteCLC) { 821 mClassLoaderContext = dexUseInfo.mClassLoaderContext; 822 } else if (isUnsupportedContext(mClassLoaderContext)) { 823 mClassLoaderContext = dexUseInfo.mClassLoaderContext; 824 } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { 825 // We detected a context change. 826 mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; 827 } 828 829 return updateIsas || 830 (oldIsUsedByOtherApps != mIsUsedByOtherApps) || 831 updateLoadingPackages 832 || !Objects.equals(oldClassLoaderContext, mClassLoaderContext); 833 } 834 isUnsupportedContext(String context)835 private static boolean isUnsupportedContext(String context) { 836 return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context); 837 } 838 isUsedByOtherApps()839 public boolean isUsedByOtherApps() { 840 return mIsUsedByOtherApps; 841 } 842 getOwnerUserId()843 /* package */ int getOwnerUserId() { 844 return mOwnerUserId; 845 } 846 getLoaderIsas()847 public Set<String> getLoaderIsas() { 848 return mLoaderIsas; 849 } 850 getLoadingPackages()851 public Set<String> getLoadingPackages() { 852 return mLoadingPackages; 853 } 854 getClassLoaderContext()855 public String getClassLoaderContext() { return mClassLoaderContext; } 856 isUnsupportedClassLoaderContext()857 public boolean isUnsupportedClassLoaderContext() { 858 return isUnsupportedContext(mClassLoaderContext); 859 } 860 isVariableClassLoaderContext()861 public boolean isVariableClassLoaderContext() { 862 return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); 863 } 864 } 865 } 866