xref: /aosp_15_r20/art/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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.DexUseManagerLocal.CheckedSecondaryDexInfo;
20 import static com.android.server.art.OutputArtifacts.PermissionSettings;
21 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
22 import static com.android.server.art.testing.TestingUtils.deepEq;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.Mockito.any;
27 import static org.mockito.Mockito.anyBoolean;
28 import static org.mockito.Mockito.anyInt;
29 import static org.mockito.Mockito.argThat;
30 import static org.mockito.Mockito.eq;
31 import static org.mockito.Mockito.isNull;
32 import static org.mockito.Mockito.lenient;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.os.CancellationSignal;
39 import android.os.SystemProperties;
40 import android.os.UserHandle;
41 
42 import androidx.test.filters.SmallTest;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.android.server.art.model.ArtFlags;
46 import com.android.server.art.model.Config;
47 import com.android.server.art.model.DexoptParams;
48 import com.android.server.art.model.DexoptResult;
49 import com.android.server.art.testing.StaticMockitoRule;
50 import com.android.server.art.testing.TestingUtils;
51 import com.android.server.pm.PackageSetting;
52 import com.android.server.pm.pkg.AndroidPackage;
53 import com.android.server.pm.pkg.PackageState;
54 import com.android.server.pm.pkg.PackageStateUnserialized;
55 
56 import org.junit.Before;
57 import org.junit.Rule;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.Mock;
61 
62 import java.util.List;
63 import java.util.Set;
64 import java.util.concurrent.ThreadPoolExecutor;
65 import java.util.function.Function;
66 
67 @SmallTest
68 @RunWith(AndroidJUnit4.class)
69 public class SecondaryDexopterTest {
70     private static final String PKG_NAME = "com.example.foo";
71     private static final int APP_ID = 12345;
72     private static final UserHandle USER_HANDLE = UserHandle.of(2);
73     private static final int UID = USER_HANDLE.getUid(APP_ID);
74     private static final String APP_DATA_DIR = "/data/user/2/" + PKG_NAME;
75     private static final String DEX_1 = APP_DATA_DIR + "/1.apk";
76     private static final String DEX_2 = APP_DATA_DIR + "/2.apk";
77     private static final String DEX_3 = APP_DATA_DIR + "/3.apk";
78 
79     private final DexoptParams mDexoptParams =
80             new DexoptParams.Builder("bg-dexopt")
81                     .setCompilerFilter("speed-profile")
82                     .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX)
83                     .build();
84 
85     private final ProfilePath mDex1RefProfile =
86             AidlUtils.buildProfilePathForSecondaryRefAsInput(DEX_1);
87     private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1);
88     private final ProfilePath mDex2RefProfile =
89             AidlUtils.buildProfilePathForSecondaryRefAsInput(DEX_2);
90     private final ProfilePath mDex3RefProfile =
91             AidlUtils.buildProfilePathForSecondaryRefAsInput(DEX_3);
92     private final OutputProfile mDex1PrivateOutputProfile =
93             AidlUtils.buildOutputProfileForSecondary(
94                     DEX_1, UID, UID, false /* isOtherReadable */, false /* isPreReboot */);
95 
96     private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
97             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
98     private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
99             | DexoptTrigger.COMPILER_FILTER_IS_SAME
100             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
101 
102     private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
103 
104     @Rule
105     public StaticMockitoRule mockitoRule =
106             new StaticMockitoRule(SystemProperties.class, Constants.class);
107 
108     @Mock private SecondaryDexopter.Injector mInjector;
109     @Mock private IArtd mArtd;
110     @Mock private DexUseManagerLocal mDexUseManager;
111     @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector;
112     @Mock private ThreadPoolExecutor mReporterExecutor;
113     private PackageState mPkgState;
114     private AndroidPackage mPkg;
115     private CancellationSignal mCancellationSignal;
116     private Config mConfig;
117     private DexMetadataHelper mDexMetadataHelper;
118 
119     private SecondaryDexopter mSecondaryDexopter;
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         mPkgState = createPackageState();
124         mPkg = mPkgState.getAndroidPackage();
125         mCancellationSignal = new CancellationSignal();
126         mConfig = new Config();
127         mDexMetadataHelper = new DexMetadataHelper(mDexMetadataHelperInjector);
128 
129         lenient()
130                 .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
131                 .thenReturn(false);
132         lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
133         lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
134 
135         // No ISA translation.
136         lenient()
137                 .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
138                 .thenReturn("");
139 
140         lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
141         lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
142         lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
143 
144         lenient().when(mInjector.getArtd()).thenReturn(mArtd);
145         lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
146         lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false);
147         lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
148         lenient().when(mInjector.getConfig()).thenReturn(mConfig);
149         lenient().when(mInjector.getReporterExecutor()).thenReturn(mReporterExecutor);
150         lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
151 
152         List<CheckedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
153         lenient()
154                 .when(mDexUseManager.getCheckedSecondaryDexInfo(
155                         eq(PKG_NAME), eq(true) /* excludeObsoleteDexesAndLoaders */))
156                 .thenReturn(secondaryDexInfo);
157 
158         prepareProfiles();
159 
160         // Dexopt is always needed and successful.
161         lenient()
162                 .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
163                 .thenReturn(dexoptIsNeeded());
164         lenient()
165                 .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
166                         any(), any()))
167                 .thenReturn(createArtdDexoptResult());
168 
169         lenient()
170                 .when(mArtd.createCancellationSignal())
171                 .thenReturn(mock(IArtdCancellationSignal.class));
172 
173         mSecondaryDexopter = new SecondaryDexopter(
174                 mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
175     }
176 
177     @Test
testDexopt()178     public void testDexopt() throws Exception {
179         assertThat(mSecondaryDexopter.dexopt())
180                 .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
181                 .containsExactly(
182                         DexContainerFileDexoptResult.create(DEX_1, true /* isPrimaryAbi */,
183                                 "arm64-v8a", "speed-profile", DexoptResult.DEXOPT_PERFORMED),
184                         DexContainerFileDexoptResult.create(DEX_2, true /* isPrimaryAbi */,
185                                 "arm64-v8a", "speed", DexoptResult.DEXOPT_PERFORMED),
186                         DexContainerFileDexoptResult.create(DEX_2, false /* isPrimaryAbi */,
187                                 "armeabi-v7a", "speed", DexoptResult.DEXOPT_PERFORMED),
188                         DexContainerFileDexoptResult.create(DEX_3, true /* isPrimaryAbi */,
189                                 "arm64-v8a", "verify", DexoptResult.DEXOPT_PERFORMED));
190 
191         // It should use profile for dex 1.
192 
193         verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile),
194                 deepEq(mDex1PrivateOutputProfile), deepEq(List.of(DEX_1)),
195                 deepEq(mMergeProfileOptions));
196 
197         verify(mArtd).getDexoptNeeded(
198                 eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger));
199         checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64",
200                 ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1");
201 
202         verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath));
203 
204         verify(mArtd).deleteProfile(deepEq(mDex1CurProfile));
205 
206         // It should use "speed" for dex 2 for both ISAs and make the artifacts public.
207 
208         verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any());
209         verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any(), any());
210 
211         verify(mArtd).getDexoptNeeded(
212                 eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
213         checkDexoptWithNoProfile(
214                 verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
215 
216         verify(mArtd).getDexoptNeeded(
217                 eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
218         checkDexoptWithNoProfile(
219                 verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
220 
221         // It should use "verify" for dex 3 and make the artifacts private.
222 
223         verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any());
224         verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any(), any());
225 
226         verify(mArtd).getDexoptNeeded(
227                 eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger));
228         checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify",
229                 null /* classLoaderContext */, false /* isPublic */);
230     }
231 
createPackage()232     private AndroidPackage createPackage() {
233         var pkg = mock(AndroidPackage.class);
234         lenient().when(pkg.isVmSafeMode()).thenReturn(false);
235         lenient().when(pkg.isDebuggable()).thenReturn(false);
236         lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
237         lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
238         lenient().when(pkg.isNonSdkApiRequested()).thenReturn(false);
239         return pkg;
240     }
241 
createPackageState()242     private PackageState createPackageState() {
243         var pkgState = mock(PackageState.class);
244         lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
245         lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
246         lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
247         lenient().when(pkgState.getAppId()).thenReturn(APP_ID);
248         lenient().when(pkgState.getSeInfo()).thenReturn("se-info");
249         AndroidPackage pkg = createPackage();
250         lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
251         return pkgState;
252     }
253 
createSecondaryDexInfo()254     private List<CheckedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
255         // This should be compiled with profile.
256         var dex1Info = mock(CheckedSecondaryDexInfo.class);
257         lenient().when(dex1Info.dexPath()).thenReturn(DEX_1);
258         lenient().when(dex1Info.userHandle()).thenReturn(USER_HANDLE);
259         lenient().when(dex1Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_1");
260         lenient().when(dex1Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
261         lenient().when(dex1Info.isUsedByOtherApps()).thenReturn(false);
262         lenient().when(dex1Info.fileVisibility()).thenReturn(FileVisibility.OTHER_READABLE);
263 
264         // This should be compiled without profile because it's used by other apps.
265         var dex2Info = mock(CheckedSecondaryDexInfo.class);
266         lenient().when(dex2Info.dexPath()).thenReturn(DEX_2);
267         lenient().when(dex2Info.userHandle()).thenReturn(USER_HANDLE);
268         lenient().when(dex2Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_2");
269         lenient().when(dex2Info.abiNames()).thenReturn(Set.of("arm64-v8a", "armeabi-v7a"));
270         lenient().when(dex2Info.isUsedByOtherApps()).thenReturn(true);
271         lenient().when(dex2Info.fileVisibility()).thenReturn(FileVisibility.OTHER_READABLE);
272 
273         // This should be compiled with verify because the class loader context is invalid.
274         var dex3Info = mock(CheckedSecondaryDexInfo.class);
275         lenient().when(dex3Info.dexPath()).thenReturn(DEX_3);
276         lenient().when(dex3Info.userHandle()).thenReturn(USER_HANDLE);
277         lenient().when(dex3Info.classLoaderContext()).thenReturn(null);
278         lenient().when(dex3Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
279         lenient().when(dex3Info.isUsedByOtherApps()).thenReturn(false);
280         lenient().when(dex3Info.fileVisibility()).thenReturn(FileVisibility.NOT_OTHER_READABLE);
281 
282         return List.of(dex1Info, dex2Info, dex3Info);
283     }
284 
prepareProfiles()285     private void prepareProfiles() throws Exception {
286         // Profile for dex file 1 is usable.
287         lenient().when(mArtd.isProfileUsable(deepEq(mDex1RefProfile), any())).thenReturn(true);
288         lenient()
289                 .when(mArtd.getProfileVisibility(deepEq(mDex1RefProfile)))
290                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
291 
292         // Profiles for dex file 2 and 3 are also usable, but shouldn't be used.
293         lenient().when(mArtd.isProfileUsable(deepEq(mDex2RefProfile), any())).thenReturn(true);
294         lenient()
295                 .when(mArtd.getProfileVisibility(deepEq(mDex2RefProfile)))
296                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
297         lenient().when(mArtd.isProfileUsable(deepEq(mDex3RefProfile), any())).thenReturn(true);
298         lenient()
299                 .when(mArtd.getProfileVisibility(deepEq(mDex3RefProfile)))
300                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
301 
302         lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
303 
304         // By default, none of the embedded profiles are usable.
305         lenient()
306                 .when(mArtd.copyAndRewriteEmbeddedProfile(any(), any()))
307                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
308     }
309 
dexoptIsNeeded()310     private GetDexoptNeededResult dexoptIsNeeded() {
311         var result = new GetDexoptNeededResult();
312         result.isDexoptNeeded = true;
313         result.artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
314         result.isVdexUsable = false;
315         result.hasDexCode = true;
316         return result;
317     }
318 
createArtdDexoptResult()319     private ArtdDexoptResult createArtdDexoptResult() {
320         var result = new ArtdDexoptResult();
321         result.cancelled = false;
322         result.wallTimeMs = 0;
323         result.cpuTimeMs = 0;
324         result.sizeBytes = 0;
325         result.sizeBeforeBytes = 0;
326         return result;
327     }
328 
checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa, ProfilePath profile, String classLoaderContext)329     private void checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa,
330             ProfilePath profile, String classLoaderContext) throws Exception {
331         PermissionSettings permissionSettings = buildPermissionSettings(false /* isPublic */);
332         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(dexPath, isa,
333                 false /* isInDalvikCache */, permissionSettings, false /* isPreReboot */);
334         artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
335                 eq("speed-profile"), deepEq(profile), any(), isNull() /* dmFile */, anyInt(),
336                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
337     }
338 
checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa, String compilerFilter, String classLoaderContext, boolean isPublic)339     private void checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa,
340             String compilerFilter, String classLoaderContext, boolean isPublic) throws Exception {
341         PermissionSettings permissionSettings = buildPermissionSettings(isPublic);
342         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(dexPath, isa,
343                 false /* isInDalvikCache */, permissionSettings, false /* isPreReboot */);
344         artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
345                 eq(compilerFilter), isNull(), any(), isNull() /* dmFile */, anyInt(),
346                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
347     }
348 
buildPermissionSettings(boolean isPublic)349     private PermissionSettings buildPermissionSettings(boolean isPublic) {
350         FsPermission dirFsPermission = AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */,
351                 false /* isOtherReadable */, true /* isOtherExecutable */);
352         FsPermission fileFsPermission =
353                 AidlUtils.buildFsPermission(UID /* uid */, UID /* gid */, isPublic);
354         return AidlUtils.buildPermissionSettings(
355                 dirFsPermission, fileFsPermission, AidlUtils.buildSeContext("se-info", UID));
356     }
357 }
358