• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.art;
18 
19 import static com.android.server.art.ArtManagerLocal.AdjustCompilerFilterCallback;
20 import static com.android.server.art.DexMetadataHelper.DexMetadataInfo;
21 import static com.android.server.art.OutputArtifacts.PermissionSettings;
22 import static com.android.server.art.ProfilePath.TmpProfilePath;
23 import static com.android.server.art.Utils.Abi;
24 import static com.android.server.art.Utils.InitProfileResult;
25 import static com.android.server.art.model.ArtFlags.DexoptFlags;
26 import static com.android.server.art.model.Config.Callback;
27 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.content.Context;
32 import android.content.pm.ApplicationInfo;
33 import android.os.Build;
34 import android.os.CancellationSignal;
35 import android.os.RemoteException;
36 import android.os.ServiceSpecificException;
37 import android.os.SystemProperties;
38 import android.os.UserManager;
39 import android.os.storage.StorageManager;
40 
41 import androidx.annotation.RequiresApi;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.server.LocalManagerRegistry;
45 import com.android.server.art.Dex2OatStatsReporter.Dex2OatResult;
46 import com.android.server.art.model.ArtFlags;
47 import com.android.server.art.model.Config;
48 import com.android.server.art.model.DetailedDexInfo;
49 import com.android.server.art.model.DexoptParams;
50 import com.android.server.art.model.DexoptResult;
51 import com.android.server.pm.PackageManagerLocal;
52 import com.android.server.pm.pkg.AndroidPackage;
53 import com.android.server.pm.pkg.PackageState;
54 
55 import dalvik.system.DexFile;
56 
57 import com.google.auto.value.AutoValue;
58 
59 import java.io.IOException;
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Objects;
64 import java.util.concurrent.Executor;
65 import java.util.regex.Matcher;
66 import java.util.regex.Pattern;
67 
68 /** @hide */
69 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
70 public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
71     private static final List<String> ART_PACKAGE_NAMES =
72             List.of("com.google.android.art", "com.android.art", "com.google.android.go.art");
73 
74     @NonNull protected final Injector mInjector;
75     @NonNull protected final PackageState mPkgState;
76     /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */
77     @NonNull protected final AndroidPackage mPkg;
78     @NonNull protected final DexoptParams mParams;
79     @NonNull protected final CancellationSignal mCancellationSignal;
80 
Dexopter(@onNull Injector injector, @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)81     protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
82             @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
83             @NonNull CancellationSignal cancellationSignal) {
84         mInjector = injector;
85         mPkgState = pkgState;
86         mPkg = pkg;
87         mParams = params;
88         mCancellationSignal = cancellationSignal;
89     }
90 
91     /**
92      * DO NOT use this method directly. Use {@link
93      * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String,
94      * DexoptParams)}.
95      */
96     @NonNull
dexopt()97     public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException {
98         if (SystemProperties.getBoolean("dalvik.vm.disable-art-service-dexopt", false /* def */)) {
99             AsLog.i("Dexopt skipped because it's disabled by system property");
100             return List.of();
101         }
102 
103         List<DexContainerFileDexoptResult> results = new ArrayList<>();
104 
105         boolean isInDalvikCache = isInDalvikCache();
106 
107         for (DexInfoType dexInfo : getDexInfoList()) {
108             ProfilePath profile = null;
109             boolean succeeded = true;
110             List<String> externalProfileErrors = List.of();
111             try {
112                 if (!isDexoptable(dexInfo)) {
113                     continue;
114                 }
115 
116                 onDexoptStart(dexInfo);
117 
118                 String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
119                 DexMetadataInfo dmInfo =
120                         mInjector.getDexMetadataHelper().getDexMetadataInfo(buildDmPath(dexInfo));
121                 if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
122                     mInjector.getReporterExecutor().execute(
123                             ()
124                                     -> Dex2OatStatsReporter.reportSkipped(mPkgState.getAppId(),
125                                             mParams.getReason(), dmInfo.type(), dexInfo,
126                                             getAllAbis(dexInfo)));
127                     continue;
128                 }
129 
130                 if (mInjector.isPreReboot() && !isDexFileFound(dexInfo)) {
131                     // In the pre-reboot case, it's possible that a dex file doesn't exist in the
132                     // new system image. Although code below can gracefully handle failures, those
133                     // failures can be red herrings in metrics and bug reports, so we skip
134                     // non-existing dex files to avoid them.
135                     continue;
136                 }
137 
138                 boolean needsToBeShared = needsToBeShared(dexInfo);
139                 boolean isOtherReadable = true;
140                 // If true, implies that the profile has changed since the last compilation.
141                 boolean profileMerged = false;
142                 if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
143                     if (!dmInfo.config().getEnableEmbeddedProfile()) {
144                         String dmPath = DexMetadataHelper.getDmPath(
145                                 Objects.requireNonNull(dmInfo.dmPath()));
146                         AsLog.i("Embedded profile disabled by config in the dm file " + dmPath);
147                     }
148 
149                     if (needsToBeShared) {
150                         InitProfileResult result = initReferenceProfile(
151                                 dexInfo, dmInfo.config().getEnableEmbeddedProfile());
152                         profile = result.profile();
153                         isOtherReadable = result.isOtherReadable();
154                         externalProfileErrors = result.externalProfileErrors();
155                     } else {
156                         InitProfileResult result = getOrInitReferenceProfile(
157                                 dexInfo, dmInfo.config().getEnableEmbeddedProfile());
158                         profile = result.profile();
159                         isOtherReadable = result.isOtherReadable();
160                         externalProfileErrors = result.externalProfileErrors();
161                         ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
162                         if (mergedProfile != null) {
163                             if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
164                                 mInjector.getArtd().deleteProfile(profile);
165                             }
166                             profile = mergedProfile;
167                             isOtherReadable = false;
168                             profileMerged = true;
169                         }
170                     }
171                     if (profile == null) {
172                         // A profile guided dexopt with no profile is essentially 'verify',
173                         // and dex2oat already makes this transformation. However, we need to
174                         // explicitly make this transformation here to guide the later decisions
175                         // such as whether the artifacts can be public and whether dexopt is needed.
176                         compilerFilter = printAdjustCompilerFilterReason(compilerFilter,
177                                 needsToBeShared ? ReasonMapping.getCompilerFilterForShared()
178                                                 : "verify",
179                                 "there is no valid profile"
180                                         + (needsToBeShared ? " and the package needs to be shared"
181                                                            : ""));
182                     }
183                 }
184                 boolean isProfileGuidedCompilerFilter =
185                         DexFile.isProfileGuidedCompilerFilter(compilerFilter);
186                 Utils.check(isProfileGuidedCompilerFilter == (profile != null));
187 
188                 boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
189                         && isDexFilePublic(dexInfo);
190                 Utils.check(Utils.implies(needsToBeShared, canBePublic));
191                 PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
192 
193                 DexoptOptions dexoptOptions =
194                         getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter);
195 
196                 for (Abi abi : getAllAbis(dexInfo)) {
197                     @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED;
198                     long wallTimeMs = 0;
199                     long cpuTimeMs = 0;
200                     long sizeBytes = 0;
201                     long sizeBeforeBytes = 0;
202                     Dex2OatResult dex2OatResult = Dex2OatResult.notRun();
203                     @DexoptResult.DexoptResultExtendedStatusFlags int extendedStatusFlags = 0;
204                     DexoptTarget<DexInfoType> target = null;
205                     try {
206                         target = DexoptTarget.<DexInfoType>builder()
207                                          .setDexInfo(dexInfo)
208                                          .setIsa(abi.isa())
209                                          .setIsInDalvikCache(isInDalvikCache)
210                                          .setCompilerFilter(compilerFilter)
211                                          .setDmPath(dmInfo.dmPath())
212                                          .build();
213                         var options = GetDexoptNeededOptions.builder()
214                                               .setProfileMerged(profileMerged)
215                                               .setFlags(mParams.getFlags())
216                                               .setNeedsToBePublic(needsToBeShared)
217                                               .build();
218 
219                         if (mInjector.isPreReboot()) {
220                             ArtifactsPath existingArtifacts =
221                                     AidlUtils.buildArtifactsPathAsInputPreReboot(
222                                             target.dexInfo().dexPath(), target.isa(),
223                                             target.isInDalvikCache());
224                             if (mInjector.getArtd().getArtifactsVisibility(existingArtifacts)
225                                     != FileVisibility.NOT_FOUND) {
226                                 // Because `getDexoptNeeded` doesn't check Pre-reboot artifacts, we
227                                 // do a simple check here to handle job resuming. If the Pre-reboot
228                                 // artifacts exist, we assume they are up-to-date because
229                                 // `PreRebootDexoptJob` would otherwise clean them up, so we skip
230                                 // this dex file. The profile and the dex file may have been changed
231                                 // since the last cancelled job run, but we don't handle such cases
232                                 // because we are supposed to dexopt every dex file only once for
233                                 // each ISA.
234                                 extendedStatusFlags |=
235                                         DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST;
236                                 continue;
237                             }
238                         }
239 
240                         GetDexoptNeededResult getDexoptNeededResult =
241                                 getDexoptNeeded(target, options);
242 
243                         if (!getDexoptNeededResult.hasDexCode) {
244                             extendedStatusFlags |= DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE;
245                         }
246 
247                         if (!getDexoptNeededResult.isDexoptNeeded) {
248                             continue;
249                         }
250 
251                         try {
252                             // `StorageManager.getAllocatableBytes` returns (free space + space used
253                             // by clearable cache - low storage threshold). Since we only compare
254                             // the result with 0, the clearable cache doesn't make a difference.
255                             // When the free space is below the threshold, there should be no
256                             // clearable cache left because system cleans up cache every minute.
257                             if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0
258                                     && mInjector.getStorageManager().getAllocatableBytes(
259                                                mPkg.getStorageUuid())
260                                             <= 0) {
261                                 extendedStatusFlags |= DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW;
262                                 continue;
263                             }
264                         } catch (IOException e) {
265                             AsLog.e("Failed to check storage. Assuming storage not low", e);
266                         }
267 
268                         IArtdCancellationSignal artdCancellationSignal =
269                                 mInjector.getArtd().createCancellationSignal();
270                         mCancellationSignal.setOnCancelListener(() -> {
271                             try {
272                                 artdCancellationSignal.cancel();
273                             } catch (RemoteException e) {
274                                 AsLog.e("An error occurred when sending a cancellation signal", e);
275                             }
276                         });
277 
278                         ArtdDexoptResult dexoptResult = dexoptFile(target, profile,
279                                 getDexoptNeededResult, permissionSettings,
280                                 mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
281                         status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED
282                                                         : DexoptResult.DEXOPT_PERFORMED;
283                         wallTimeMs = dexoptResult.wallTimeMs;
284                         cpuTimeMs = dexoptResult.cpuTimeMs;
285                         sizeBytes = dexoptResult.sizeBytes;
286                         sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
287                         dex2OatResult = dexoptResult.cancelled ? Dex2OatResult.cancelled()
288                                                                : Dex2OatResult.exited(0);
289 
290                         if (status == DexoptResult.DEXOPT_CANCELLED) {
291                             return results;
292                         }
293                     } catch (ServiceSpecificException e) {
294                         // Log the error and continue.
295                         AsLog.e(String.format("Failed to dexopt [packageName = %s, dexPath = %s, "
296                                                 + "isa = %s, classLoaderContext = %s]",
297                                         mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
298                                         dexInfo.classLoaderContext()),
299                                 e);
300                         status = DexoptResult.DEXOPT_FAILED;
301 
302                         // Parse status, exit code and signal from the dex2oat error message
303                         Pattern pattern = Pattern.compile(
304                                 "\\[status=(-?\\d+),exit_code=(-?\\d+),signal=(-?\\d+)]");
305                         Matcher matcher = pattern.matcher(Objects.requireNonNull(e.getMessage()));
306                         if (matcher.matches()) {
307                             dex2OatResult = new Dex2OatResult(Integer.parseInt(matcher.group(1)),
308                                     Integer.parseInt(matcher.group(2)),
309                                     Integer.parseInt(matcher.group(3)));
310                         }
311                     } finally {
312                         if (!externalProfileErrors.isEmpty()) {
313                             extendedStatusFlags |= DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE;
314                         }
315                         var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(),
316                                 abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
317                                 cpuTimeMs, sizeBytes, sizeBeforeBytes, extendedStatusFlags,
318                                 externalProfileErrors);
319                         AsLog.i(String.format("Dexopt result: [packageName = %s] %s",
320                                 mPkgState.getPackageName(), result));
321                         results.add(result);
322 
323                         onDexoptTargetResult(target, status);
324 
325                         if (status != DexoptResult.DEXOPT_SKIPPED
326                                 && status != DexoptResult.DEXOPT_PERFORMED) {
327                             succeeded = false;
328                         }
329                         // Make sure artd does not leak even if the caller holds
330                         // `mCancellationSignal` forever.
331                         mCancellationSignal.setOnCancelListener(null);
332 
333                         // Variables used in lambda needs to be effectively final.
334                         Dex2OatResult finalDex2OatResult = dex2OatResult;
335                         mInjector.getReporterExecutor().execute(
336                                 ()
337                                         -> Dex2OatStatsReporter.report(mPkgState.getAppId(),
338                                                 result.getActualCompilerFilter(),
339                                                 mParams.getReason(), dmInfo.type(), dexInfo,
340                                                 abi.isa(), finalDex2OatResult,
341                                                 result.getSizeBytes(),
342                                                 result.getDex2oatWallTimeMillis()));
343                     }
344                 }
345 
346                 if (profile != null && succeeded) {
347                     if (profile.getTag() == ProfilePath.tmpProfilePath) {
348                         // Commit the profile only if dexopt succeeds.
349                         if (commitProfileChanges(profile.getTmpProfilePath())) {
350                             profile = null;
351                         }
352                     }
353                     // We keep the current profiles in the Pre-reboot Dexopt case, to leave it to
354                     // background dexopt.
355                     if (profileMerged && !mInjector.isPreReboot()) {
356                         // Note that this is just an optimization, to reduce the amount of data that
357                         // the runtime writes on every profile save. The profile merge result on the
358                         // next run won't change regardless of whether the cleanup is done or not
359                         // because profman only looks at the diff.
360                         // A caveat is that it may delete more than what has been merged, if the
361                         // runtime writes additional entries between the merge and the cleanup, but
362                         // this is fine because the runtime writes all JITed classes and methods on
363                         // every save and the additional entries will likely be written back on the
364                         // next save.
365                         cleanupCurProfiles(dexInfo);
366                     }
367                 }
368             } finally {
369                 if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
370                     mInjector.getArtd().deleteProfile(profile);
371                 }
372             }
373         }
374 
375         return results;
376     }
377 
378     // The javadoc on `AdjustCompilerFilterCallback.onAdjustCompilerFilter` may need updating when
379     // this method is changed.
380     @NonNull
adjustCompilerFilter( @onNull String targetCompilerFilter, @NonNull DexInfoType dexInfo)381     private String adjustCompilerFilter(
382             @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
383         if ((mParams.getFlags() & ArtFlags.FLAG_FORCE_COMPILER_FILTER) == 0) {
384             if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
385                 String systemUiCompilerFilter = getSystemUiCompilerFilter();
386                 if (!systemUiCompilerFilter.isEmpty()) {
387                     targetCompilerFilter = printAdjustCompilerFilterReason(targetCompilerFilter,
388                             systemUiCompilerFilter, "the package is System UI");
389                 }
390             } else if (mInjector.isLauncherPackage(mPkgState.getPackageName())) {
391                 targetCompilerFilter = printAdjustCompilerFilterReason(
392                         targetCompilerFilter, "speed-profile", "the package is a launcher package");
393             }
394 
395             Callback<AdjustCompilerFilterCallback, Void> callback =
396                     mInjector.getConfig().getAdjustCompilerFilterCallback();
397             if (callback != null) {
398                 // Local variables passed to the lambda must be final or effectively final.
399                 final String originalCompilerFilter = targetCompilerFilter;
400                 targetCompilerFilter = printAdjustCompilerFilterReason(
401                         targetCompilerFilter, Utils.executeAndWait(callback.executor(), () -> {
402                             return callback.get().onAdjustCompilerFilter(mPkgState.getPackageName(),
403                                     originalCompilerFilter, mParams.getReason());
404                         }), "of AdjustCompilerFilterCallback");
405             }
406         }
407 
408         // Code below should only downgrade the compiler filter. Don't upgrade the compiler filter
409         // beyond this point!
410 
411         // We force vmSafeMode on debuggable apps as well:
412         //  - the runtime ignores their compiled code
413         //  - they generally have lots of methods that could make the compiler used run out of
414         //    memory (b/130828957)
415         // Note that forcing the compiler filter here applies to all compilations (even if they
416         // are done via adb shell commands). This is okay because the runtime will ignore the
417         // compiled code anyway.
418         if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
419             targetCompilerFilter = printAdjustCompilerFilterReason(targetCompilerFilter,
420                     DexFile.getSafeModeCompilerFilter(targetCompilerFilter),
421                     mPkg.isVmSafeMode() ? "the package requests VM safe mode"
422                                         : "the package is debuggable");
423         }
424 
425         // We cannot do AOT compilation if we don't have a valid class loader context.
426         if (dexInfo.classLoaderContext() == null
427                 && DexFile.isOptimizedCompilerFilter(targetCompilerFilter)) {
428             targetCompilerFilter = printAdjustCompilerFilterReason(
429                     targetCompilerFilter, "verify", "there is no valid class loader context");
430         }
431 
432         // This application wants to use the embedded dex in the APK, rather than extracted or
433         // locally compiled variants, so we only verify it.
434         // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
435         // won't extract the dex code because the APK is uncompressed, and the assumption is that
436         // such applications always use uncompressed APKs.
437         if (mPkg.isUseEmbeddedDex() && DexFile.isOptimizedCompilerFilter(targetCompilerFilter)) {
438             targetCompilerFilter = printAdjustCompilerFilterReason(
439                     targetCompilerFilter, "verify", "the package requests to use embedded dex");
440         }
441 
442         if ((mParams.getFlags() & ArtFlags.FLAG_IGNORE_PROFILE) != 0
443                 && DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
444             targetCompilerFilter = printAdjustCompilerFilterReason(
445                     targetCompilerFilter, "verify", "the user requests to ignore the profile");
446         }
447 
448         return targetCompilerFilter;
449     }
450 
451     @NonNull
getSystemUiCompilerFilter()452     private String getSystemUiCompilerFilter() {
453         String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
454         if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
455             throw new IllegalStateException(
456                     "Got invalid compiler filter '" + compilerFilter + "' for System UI");
457         }
458         return compilerFilter;
459     }
460 
printAdjustCompilerFilterReason(@onNull String oldCompilerFilter, @NonNull String newCompilerFilter, @NonNull String reason)461     private @NonNull String printAdjustCompilerFilterReason(@NonNull String oldCompilerFilter,
462             @NonNull String newCompilerFilter, @NonNull String reason) {
463         if (!oldCompilerFilter.equals(newCompilerFilter)) {
464             AsLog.i(String.format(
465                     "Adjusting the compiler filter for '%s' from '%s' to '%s' because %s",
466                     mPkgState.getPackageName(), oldCompilerFilter, newCompilerFilter, reason));
467         }
468         return newCompilerFilter;
469     }
470 
471     /** @see Utils#getOrInitReferenceProfile */
472     @Nullable
getOrInitReferenceProfile( @onNull DexInfoType dexInfo, boolean enableEmbeddedProfile)473     private InitProfileResult getOrInitReferenceProfile(
474             @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException {
475         return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
476                 buildRefProfilePathAsInput(dexInfo), getExternalProfiles(dexInfo),
477                 enableEmbeddedProfile, buildOutputProfile(dexInfo, true /* isPublic */));
478     }
479 
480     @Nullable
initReferenceProfile( @onNull DexInfoType dexInfo, boolean enableEmbeddedProfile)481     private InitProfileResult initReferenceProfile(
482             @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException {
483         return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
484                 getExternalProfiles(dexInfo), enableEmbeddedProfile,
485                 buildOutputProfile(dexInfo, true /* isPublic */));
486     }
487 
488     @NonNull
getDexoptOptions( @onNull DexInfoType dexInfo, boolean isProfileGuidedFilter)489     private DexoptOptions getDexoptOptions(
490             @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
491         DexoptOptions dexoptOptions = new DexoptOptions();
492         dexoptOptions.compilationReason = mParams.getReason();
493         dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
494         dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
495         // Generating a meaningful app image needs a profile to determine what to include in the
496         // image. Otherwise, the app image will be nearly empty.
497         dexoptOptions.generateAppImage = isProfileGuidedFilter && isAppImageEnabled();
498         dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
499         dexoptOptions.comments =
500                 String.format("app-name:%s,app-version-name:%s,app-version-code:%d,art-version:%d",
501                         mPkgState.getPackageName(), mPkg.getVersionName(),
502                         mPkg.getLongVersionCode(), mInjector.getArtVersion());
503         return dexoptOptions;
504     }
505 
isAlwaysDebuggable()506     private boolean isAlwaysDebuggable() {
507         return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
508     }
509 
isAppImageEnabled()510     private boolean isAppImageEnabled() {
511         return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
512     }
513 
isHiddenApiPolicyEnabled()514     private boolean isHiddenApiPolicyEnabled() {
515         return mPkgState.getHiddenApiEnforcementPolicy()
516                 != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
517     }
518 
519     @NonNull
getDexoptNeeded(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)520     GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
521             @NonNull GetDexoptNeededOptions options) throws RemoteException {
522         int dexoptTrigger = getDexoptTrigger(target, options);
523 
524         // The result should come from artd even if all the bits of `dexoptTrigger` are set
525         // because the result also contains information about the usable VDEX file.
526         // Note that the class loader context can be null. In that case, we intentionally pass the
527         // null value down to lower levels to indicate that the class loader context check should be
528         // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
529         GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
530                 target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
531                 target.compilerFilter(), dexoptTrigger);
532 
533         return result;
534     }
535 
getDexoptTrigger(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)536     int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
537             @NonNull GetDexoptNeededOptions options) throws RemoteException {
538         if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
539             return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
540                     | DexoptTrigger.COMPILER_FILTER_IS_WORSE
541                     | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
542                     | DexoptTrigger.NEED_EXTRACTION;
543         }
544 
545         if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
546             return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
547         }
548 
549         int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
550                 | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
551         if (options.profileMerged()) {
552             dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
553         }
554 
555         ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPathAsInput(
556                 target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
557 
558         if (options.needsToBePublic()
559                 && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
560                         == FileVisibility.NOT_OTHER_READABLE) {
561             // Typically, this happens after an app starts being used by other apps.
562             // This case should be the same as force as we have no choice but to trigger a new
563             // dexopt.
564             dexoptTrigger |=
565                     DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
566         }
567 
568         return dexoptTrigger;
569     }
570 
dexoptFile(@onNull DexoptTarget<DexInfoType> target, @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult, @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass, @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)571     private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
572             @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
573             @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
574             @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
575             throws RemoteException {
576         OutputArtifacts outputArtifacts =
577                 AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(), target.isa(),
578                         target.isInDalvikCache(), permissionSettings, mInjector.isPreReboot());
579 
580         VdexPath inputVdex =
581                 getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
582 
583         if (target.dmPath() != null
584                 && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
585             // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
586             // "install-dm").
587             // Note that this only applies to reasons for app install because the goal is to give
588             // Play a signal that a DM file is downloaded at install time. We actually pass the DM
589             // file regardless of the compilation reason, but we don't append a suffix when the
590             // compilation reason is not a reason for app install.
591             // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
592             // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
593             // compilation reason can still be "install-dm" even if dex2oat left all contents of the
594             // DM file unused or an empty DM file is passed to dex2oat.
595             dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
596         }
597 
598         ArtdDexoptResult result = mInjector.getArtd().dexopt(outputArtifacts,
599                 target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
600                 target.compilerFilter(), profile, inputVdex, target.dmPath(), priorityClass,
601                 dexoptOptions, artdCancellationSignal);
602 
603         // Delete the existing runtime images after the dexopt is performed, even if they are still
604         // usable (e.g., the compiler filter is "verify"). This is to make sure the dexopt puts the
605         // dex file into a certain dexopt state, to make it easier for debugging and testing. It's
606         // also an optimization to release disk space as soon as possible. However, not doing the
607         // deletion here does not affect correctness or waste disk space: if the existing runtime
608         // images are still usable, technically, they can still be used to improve runtime
609         // performance; if they are no longer usable, they will be deleted by the file GC during the
610         // daily background dexopt job anyway.
611         // We keep the runtime artifacts in the Pre-reboot Dexopt case because they are still needed
612         // before the reboot.
613         if (!result.cancelled && !mInjector.isPreReboot()) {
614             mInjector.getArtd().deleteRuntimeArtifacts(AidlUtils.buildRuntimeArtifactsPath(
615                     mPkgState.getPackageName(), target.dexInfo().dexPath(), target.isa()));
616         }
617 
618         return result;
619     }
620 
621     @Nullable
getInputVdex(@onNull GetDexoptNeededResult getDexoptNeededResult, @NonNull String dexPath, @NonNull String isa)622     private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
623             @NonNull String dexPath, @NonNull String isa) {
624         if (!getDexoptNeededResult.isVdexUsable) {
625             return null;
626         }
627         switch (getDexoptNeededResult.artifactsLocation) {
628             case ArtifactsLocation.DALVIK_CACHE:
629                 return VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
630                         dexPath, isa, true /* isInDalvikCache */));
631             case ArtifactsLocation.NEXT_TO_DEX:
632                 return VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
633                         dexPath, isa, false /* isInDalvikCache */));
634             case ArtifactsLocation.DM:
635             case ArtifactsLocation.SDM_DALVIK_CACHE:
636             case ArtifactsLocation.SDM_NEXT_TO_DEX:
637                 // In these cases, the VDEX file is in the DM file. The whole DM file is passed to
638                 // dex2oat as a separate flag whenever it exists.
639                 return null;
640             default:
641                 // This should never happen as the value is got from artd.
642                 throw new IllegalStateException(
643                         "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
644         }
645     }
646 
commitProfileChanges(@onNull TmpProfilePath profile)647     private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
648         try {
649             mInjector.getArtd().commitTmpProfile(profile);
650             return true;
651         } catch (ServiceSpecificException e) {
652             AsLog.e("Failed to commit profile changes " + AidlUtils.toString(profile.finalPath), e);
653             return false;
654         }
655     }
656 
657     @Nullable
mergeProfiles(@onNull DexInfoType dexInfo, @Nullable ProfilePath referenceProfile)658     private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
659             @Nullable ProfilePath referenceProfile) throws RemoteException {
660         OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
661 
662         var options = new MergeProfileOptions();
663         options.forceMerge = (mParams.getFlags() & ArtFlags.FLAG_FORCE_MERGE_PROFILE) != 0;
664 
665         try {
666             if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
667                         List.of(dexInfo.dexPath()), options)) {
668                 return ProfilePath.tmpProfilePath(output.profilePath);
669             }
670         } catch (ServiceSpecificException e) {
671             AsLog.e("Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
672                     e);
673         }
674 
675         return null;
676     }
677 
cleanupCurProfiles(@onNull DexInfoType dexInfo)678     private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
679         for (ProfilePath profile : getCurProfiles(dexInfo)) {
680             mInjector.getArtd().deleteProfile(profile);
681         }
682     }
683 
684     // Methods to be implemented by child classes.
685 
686     /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
isInDalvikCache()687     protected abstract boolean isInDalvikCache() throws RemoteException;
688 
689     /** Returns information about all dex files. */
getDexInfoList()690     @NonNull protected abstract List<DexInfoType> getDexInfoList();
691 
692     /** Returns true if the given dex file should be dexopted. */
isDexoptable(@onNull DexInfoType dexInfo)693     protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo);
694 
695     /**
696      * Returns true if the artifacts should be shared with other apps. Note that this must imply
697      * {@link #isDexFilePublic(DexInfoType)}.
698      */
needsToBeShared(@onNull DexInfoType dexInfo)699     protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
700 
701     /**
702      * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
703      * (S_IROTH).
704      */
isDexFilePublic(@onNull DexInfoType dexInfo)705     protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
706 
707     /**
708      * Returns true if the dex file is found.
709      */
isDexFileFound(@onNull DexInfoType dexInfo)710     protected abstract boolean isDexFileFound(@NonNull DexInfoType dexInfo);
711 
712     /**
713      * Returns a list of external profiles (e.g., a DM profile) that the reference profile can be
714      * initialized from, in the order of preference.
715      */
getExternalProfiles(@onNull DexInfoType dexInfo)716     @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo);
717 
718     /** Returns the permission settings to use for the artifacts of the given dex file. */
719     @NonNull
getPermissionSettings( @onNull DexInfoType dexInfo, boolean canBePublic)720     protected abstract PermissionSettings getPermissionSettings(
721             @NonNull DexInfoType dexInfo, boolean canBePublic);
722 
723     /** Returns all ABIs that the given dex file should be compiled for. */
getAllAbis(@onNull DexInfoType dexInfo)724     @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
725 
726     /** Returns the path to the reference profile of the given dex file. */
727     @NonNull
buildRefProfilePathAsInput(@onNull DexInfoType dexInfo)728     protected abstract ProfilePath buildRefProfilePathAsInput(@NonNull DexInfoType dexInfo);
729 
730     /**
731      * Returns the data structure that represents the temporary profile to use during processing.
732      */
733     @NonNull
buildOutputProfile( @onNull DexInfoType dexInfo, boolean isPublic)734     protected abstract OutputProfile buildOutputProfile(
735             @NonNull DexInfoType dexInfo, boolean isPublic);
736 
737     /** Returns the paths to the current profiles of the given dex file. */
getCurProfiles(@onNull DexInfoType dexInfo)738     @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
739 
740     /**
741      * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
742      * should be passed.
743      */
buildDmPath(@onNull DexInfoType dexInfo)744     @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
745 
746     /**
747      * Called at an early stage during dexopt of every dex file, even before dexopt is skipped by
748      * the noop compiler filter.
749      */
onDexoptStart(@onNull DexInfoType dexInfo)750     protected void onDexoptStart(@NonNull DexInfoType dexInfo) throws RemoteException {}
751 
752     /**
753      * Called once for every dex file and every ABI when dexopt has a result.
754      */
onDexoptTargetResult(@onNull DexoptTarget<DexInfoType> target, @DexoptResult.DexoptResultStatus int status)755     protected void onDexoptTargetResult(@NonNull DexoptTarget<DexInfoType> target,
756             @DexoptResult.DexoptResultStatus int status) throws RemoteException {}
757 
758     @AutoValue
759     abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
dexInfo()760         abstract @NonNull DexInfoType dexInfo();
isa()761         abstract @NonNull String isa();
isInDalvikCache()762         abstract boolean isInDalvikCache();
compilerFilter()763         abstract @NonNull String compilerFilter();
dmPath()764         abstract @Nullable DexMetadataPath dmPath();
765 
builder()766         static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
767             return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
768         }
769 
770         @AutoValue.Builder
771         abstract static class Builder<DexInfoType extends DetailedDexInfo> {
setDexInfo(@onNull DexInfoType value)772             abstract Builder setDexInfo(@NonNull DexInfoType value);
setIsa(@onNull String value)773             abstract Builder setIsa(@NonNull String value);
setIsInDalvikCache(boolean value)774             abstract Builder setIsInDalvikCache(boolean value);
setCompilerFilter(@onNull String value)775             abstract Builder setCompilerFilter(@NonNull String value);
setDmPath(@ullable DexMetadataPath value)776             abstract Builder setDmPath(@Nullable DexMetadataPath value);
build()777             abstract DexoptTarget<DexInfoType> build();
778         }
779     }
780 
781     @AutoValue
782     abstract static class GetDexoptNeededOptions {
flags()783         abstract @DexoptFlags int flags();
profileMerged()784         abstract boolean profileMerged();
needsToBePublic()785         abstract boolean needsToBePublic();
786 
builder()787         static Builder builder() {
788             return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder();
789         }
790 
791         @AutoValue.Builder
792         abstract static class Builder {
setFlags(@exoptFlags int value)793             abstract Builder setFlags(@DexoptFlags int value);
setProfileMerged(boolean value)794             abstract Builder setProfileMerged(boolean value);
setNeedsToBePublic(boolean value)795             abstract Builder setNeedsToBePublic(boolean value);
build()796             abstract GetDexoptNeededOptions build();
797         }
798     }
799 
800     /**
801      * Injector pattern for testing purpose.
802      *
803      * @hide
804      */
805     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
806     public static class Injector {
807         @NonNull private final Context mContext;
808         @NonNull private final Config mConfig;
809         @NonNull private final Executor mReporterExecutor;
810 
Injector(@onNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor)811         public Injector(@NonNull Context context, @NonNull Config config,
812                 @NonNull Executor reporterExecutor) {
813             mContext = context;
814             mConfig = config;
815             mReporterExecutor = reporterExecutor;
816 
817             // Call the getters for various dependencies, to ensure correct initialization order.
818             getUserManager();
819             getDexUseManager();
820             getStorageManager();
821             GlobalInjector.getInstance().checkArtModuleServiceManager();
822         }
823 
isSystemUiPackage(@onNull String packageName)824         public boolean isSystemUiPackage(@NonNull String packageName) {
825             return Utils.isSystemUiPackage(mContext, packageName);
826         }
827 
isLauncherPackage(@onNull String packageName)828         public boolean isLauncherPackage(@NonNull String packageName) {
829             return Utils.isLauncherPackage(mContext, packageName);
830         }
831 
832         @NonNull
getUserManager()833         public UserManager getUserManager() {
834             return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
835         }
836 
837         @NonNull
getDexUseManager()838         public DexUseManagerLocal getDexUseManager() {
839             return GlobalInjector.getInstance().getDexUseManager();
840         }
841 
842         @NonNull
getArtd()843         public IArtd getArtd() {
844             return ArtdRefCache.getInstance().getArtd();
845         }
846 
847         @NonNull
getStorageManager()848         public StorageManager getStorageManager() {
849             return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
850         }
851 
852         @NonNull
getPackageManagerLocal()853         private PackageManagerLocal getPackageManagerLocal() {
854             return Objects.requireNonNull(
855                     LocalManagerRegistry.getManager(PackageManagerLocal.class));
856         }
857 
getArtVersion()858         public long getArtVersion() {
859             try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) {
860                 Map<String, PackageState> packageStates = snapshot.getPackageStates();
861                 for (String artPackageName : ART_PACKAGE_NAMES) {
862                     PackageState pkgState = packageStates.get(artPackageName);
863                     if (pkgState != null) {
864                         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
865                         return pkg.getLongVersionCode();
866                     }
867                 }
868             }
869             return -1;
870         }
871 
872         @NonNull
getConfig()873         public Config getConfig() {
874             return mConfig;
875         }
876 
877         @NonNull
getReporterExecutor()878         public Executor getReporterExecutor() {
879             return mReporterExecutor;
880         }
881 
882         @NonNull
getDexMetadataHelper()883         public DexMetadataHelper getDexMetadataHelper() {
884             return new DexMetadataHelper();
885         }
886 
isPreReboot()887         public boolean isPreReboot() {
888             return GlobalInjector.getInstance().isPreReboot();
889         }
890     }
891 }
892