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