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