Support secondary dex compilation.
Bug: 249984283
Test: atest ArtServiceTests
Test: m test-art-host-gtest-art_artd_tests
Test: m test-art-host-gtest-art_runtime_tests
Test: adb shell pm art optimize-package --secondary-dex -m speed-profile -f com.google.android.gms
Ignore-AOSP-First: ART Services.
Change-Id: I7ebe2aa745d0da31242034a27f92b24dbdb08740
diff --git a/artd/artd.cc b/artd/artd.cc
index ce42a86..32c89c3 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -228,7 +228,7 @@
if (se_context.has_value()) {
res = selinux_android_restorecon_pkgdir(path.c_str(),
se_context->seInfo.c_str(),
- se_context->packageUid,
+ se_context->uid,
SELINUX_ANDROID_RESTORECON_RECURSE);
} else {
res = selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
@@ -543,6 +543,13 @@
return ScopedAStatus::ok();
}
+ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile,
+ FileVisibility* _aidl_return) {
+ OR_RETURN_FATAL(ValidateDexPath(in_dexFile));
+ *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile));
+ return ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles,
const std::optional<ProfilePath>& in_referenceProfile,
OutputProfile* in_outputProfile,
@@ -642,7 +649,7 @@
ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile,
const std::string& in_instructionSet,
- const std::string& in_classLoaderContext,
+ const std::optional<std::string>& in_classLoaderContext,
const std::string& in_compilerFilter,
int32_t in_dexoptTrigger,
GetDexoptNeededResult* _aidl_return) {
@@ -653,9 +660,9 @@
std::unique_ptr<ClassLoaderContext> context;
std::string error_msg;
- auto oat_file_assistant = OatFileAssistant::Create(in_dexFile.c_str(),
- in_instructionSet.c_str(),
- in_classLoaderContext.c_str(),
+ auto oat_file_assistant = OatFileAssistant::Create(in_dexFile,
+ in_instructionSet,
+ in_classLoaderContext,
/*load_executable=*/false,
/*only_load_trusted_executable=*/true,
ofa_context.value(),
@@ -680,7 +687,7 @@
const OutputArtifacts& in_outputArtifacts,
const std::string& in_dexFile,
const std::string& in_instructionSet,
- const std::string& in_classLoaderContext,
+ const std::optional<std::string>& in_classLoaderContext,
const std::string& in_compilerFilter,
const std::optional<ProfilePath>& in_profile,
const std::optional<VdexPath>& in_inputVdex,
@@ -701,10 +708,12 @@
ArtdCancellationSignal* cancellation_signal =
OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get()));
- std::unique_ptr<ClassLoaderContext> context =
- ClassLoaderContext::Create(in_classLoaderContext.c_str());
- if (context == nullptr) {
- return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext));
+ std::unique_ptr<ClassLoaderContext> context = nullptr;
+ if (in_classLoaderContext.has_value()) {
+ context = ClassLoaderContext::Create(in_classLoaderContext->c_str());
+ if (context == nullptr) {
+ return Fatal("Class loader context '{}' is invalid"_format(in_classLoaderContext.value()));
+ }
}
OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts));
@@ -754,21 +763,23 @@
args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile);
fd_logger.Add(*dex_file);
- std::vector<std::string> flattened_context = context->FlattenDexPaths();
- std::string dex_dir = Dirname(in_dexFile.c_str());
std::vector<std::unique_ptr<File>> context_files;
- std::vector<int> context_fds;
- for (const std::string& context_element : flattened_context) {
- std::string context_path = std::filesystem::path(dex_dir).append(context_element);
- OR_RETURN_FATAL(ValidateDexPath(context_path));
- std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path));
- context_fds.push_back(context_file->Fd());
- fd_logger.Add(*context_file);
- context_files.push_back(std::move(context_file));
+ if (context != nullptr) {
+ std::vector<std::string> flattened_context = context->FlattenDexPaths();
+ std::string dex_dir = Dirname(in_dexFile.c_str());
+ std::vector<int> context_fds;
+ for (const std::string& context_element : flattened_context) {
+ std::string context_path = std::filesystem::path(dex_dir).append(context_element);
+ OR_RETURN_FATAL(ValidateDexPath(context_path));
+ std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path));
+ context_fds.push_back(context_file->Fd());
+ fd_logger.Add(*context_file);
+ context_files.push_back(std::move(context_file));
+ }
+ args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':'))
+ .Add("--class-loader-context=%s", in_classLoaderContext.value())
+ .Add("--classpath-dir=%s", dex_dir);
}
- args.Add("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':'))
- .Add("--class-loader-context=%s", in_classLoaderContext)
- .Add("--classpath-dir=%s", dex_dir);
std::unique_ptr<File> input_vdex_file = nullptr;
if (in_inputVdex.has_value()) {
diff --git a/artd/artd.h b/artd/artd.h
index c775c4a..1230ee8 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -24,6 +24,7 @@
#include <functional>
#include <memory>
#include <mutex>
+#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
@@ -115,10 +116,14 @@
const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+ ndk::ScopedAStatus getDexFileVisibility(
+ const std::string& in_dexFile,
+ aidl::com::android::server::art::FileVisibility* _aidl_return) override;
+
ndk::ScopedAStatus getDexoptNeeded(
const std::string& in_dexFile,
const std::string& in_instructionSet,
- const std::string& in_classLoaderContext,
+ const std::optional<std::string>& in_classLoaderContext,
const std::string& in_compilerFilter,
int32_t in_dexoptTrigger,
aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override;
@@ -127,7 +132,7 @@
const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts,
const std::string& in_dexFile,
const std::string& in_instructionSet,
- const std::string& in_classLoaderContext,
+ const std::optional<std::string>& in_classLoaderContext,
const std::string& in_compilerFilter,
const std::optional<aidl::com::android::server::art::ProfilePath>& in_profile,
const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex,
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 76cba88..68d23db 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -357,7 +357,7 @@
OutputArtifacts output_artifacts_;
std::string clc_1_;
std::string clc_2_;
- std::string class_loader_context_;
+ std::optional<std::string> class_loader_context_;
std::string compiler_filter_;
std::optional<VdexPath> vdex_path_;
PriorityClass priority_class_ = PriorityClass::BACKGROUND;
@@ -526,6 +526,22 @@
RunDexopt();
}
+TEST_F(ArtdTest, dexoptClassLoaderContextNull) {
+ class_loader_context_ = std::nullopt;
+
+ EXPECT_CALL(
+ *mock_exec_utils_,
+ DoExecAndReturnCode(WhenSplitBy("--",
+ _,
+ AllOf(Not(Contains(Flag("--class-loader-context-fds=", _))),
+ Not(Contains(Flag("--class-loader-context=", _))),
+ Not(Contains(Flag("--classpath-dir=", _))))),
+ _,
+ _))
+ .WillOnce(Return(0));
+ RunDexopt();
+}
+
TEST_F(ArtdTest, dexoptNoInputVdex) {
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
@@ -1227,6 +1243,45 @@
EXPECT_THAT(status.getMessage(), ContainsRegex(R"re(Failed to get status of .*b\.odex)re"));
}
+TEST_F(ArtdTest, getDexFileVisibilityOtherReadable) {
+ CreateFile(dex_file_);
+ std::filesystem::permissions(
+ dex_file_, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getDexFileVisibilityNotOtherReadable) {
+ CreateFile(dex_file_);
+ std::filesystem::permissions(
+ dex_file_, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
+
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
+}
+
+TEST_F(ArtdTest, getDexFileVisibilityNotFound) {
+ FileVisibility result;
+ ASSERT_TRUE(artd_->getDexFileVisibility(dex_file_, &result).isOk());
+ EXPECT_EQ(result, FileVisibility::NOT_FOUND);
+}
+
+TEST_F(ArtdTest, getDexFileVisibilityPermissionDenied) {
+ CreateFile(dex_file_);
+
+ auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(dex_file_).parent_path());
+ auto scoped_unroot = ScopedUnroot();
+
+ FileVisibility result;
+ ndk::ScopedAStatus status = artd_->getDexFileVisibility(dex_file_, &result);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ EXPECT_THAT(status.getMessage(), ContainsRegex(R"re(Failed to get status of .*/a/b\.apk)re"));
+}
+
TEST_F(ArtdTest, mergeProfiles) {
const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
diff --git a/artd/binder/com/android/server/art/FileVisibility.aidl b/artd/binder/com/android/server/art/FileVisibility.aidl
index c8d1455..ceaa818 100644
--- a/artd/binder/com/android/server/art/FileVisibility.aidl
+++ b/artd/binder/com/android/server/art/FileVisibility.aidl
@@ -26,6 +26,7 @@
*
* @hide
*/
+@Backing(type="int")
enum FileVisibility {
NOT_FOUND = 0,
OTHER_READABLE = 1,
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 782334b..d575adf 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -103,6 +103,13 @@
in com.android.server.art.ArtifactsPath artifactsPath);
/**
+ * Returns the visibility of the dex file.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ com.android.server.art.FileVisibility getDexFileVisibility(@utf8InCpp String dexFile);
+
+ /**
* Returns true if dexopt is needed. `dexoptTrigger` is a bit field that consists of values
* defined in `com.android.server.art.DexoptTrigger`.
*
@@ -110,7 +117,7 @@
*/
com.android.server.art.GetDexoptNeededResult getDexoptNeeded(
@utf8InCpp String dexFile, @utf8InCpp String instructionSet,
- @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+ @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
int dexoptTrigger);
/**
@@ -121,7 +128,7 @@
com.android.server.art.DexoptResult dexopt(
in com.android.server.art.OutputArtifacts outputArtifacts,
@utf8InCpp String dexFile, @utf8InCpp String instructionSet,
- @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
+ @nullable @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
in @nullable com.android.server.art.ProfilePath profile,
in @nullable com.android.server.art.VdexPath inputVdex,
com.android.server.art.PriorityClass priorityClass,
diff --git a/artd/binder/com/android/server/art/OutputArtifacts.aidl b/artd/binder/com/android/server/art/OutputArtifacts.aidl
index 20ed475..af3e3ce 100644
--- a/artd/binder/com/android/server/art/OutputArtifacts.aidl
+++ b/artd/binder/com/android/server/art/OutputArtifacts.aidl
@@ -40,8 +40,8 @@
/** The seinfo tag in SELinux policy. */
@utf8InCpp String seInfo;
- /** The package uid. */
- int packageUid;
+ /** The uid that represents the combination of the user id and the app id. */
+ int uid;
}
/**
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index f7df305..21563aea 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -170,6 +170,14 @@
}
@NonNull
+ public static SeContext buildSeContext(@NonNull String seInfo, int uid) {
+ var seContext = new SeContext();
+ seContext.seInfo = seInfo;
+ seContext.uid = uid;
+ return seContext;
+ }
+
+ @NonNull
public static String toString(@NonNull PrimaryRefProfilePath profile) {
return String.format(
"[packageName = %s, profileName = %s]", profile.packageName, profile.profileName);
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index b9485a0..2c90fb6 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -90,6 +90,11 @@
case "-f":
paramsBuilder.setFlags(ArtFlags.FLAG_FORCE, ArtFlags.FLAG_FORCE);
break;
+ case "--secondary-dex":
+ paramsBuilder.setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+ ArtFlags.FLAG_FOR_PRIMARY_DEX
+ | ArtFlags.FLAG_FOR_SECONDARY_DEX);
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -226,6 +231,7 @@
pw.println(" Options:");
pw.println(" -m Set the compiler filter.");
pw.println(" -f Force compilation.");
+ pw.println(" --secondary-dex Only compile secondary dex.");
pw.println(" cancel JOB_ID");
pw.println(" Cancel a job.");
pw.println(" dex-use-notify PACKAGE_NAME DEX_PATH CLASS_LOADER_CONTEXT");
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
index c87d71b..bf84e48 100644
--- a/libartservice/service/java/com/android/server/art/DexOptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -109,9 +109,13 @@
}
if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
- // TODO(jiakaiz): Implement this.
- throw new UnsupportedOperationException(
- "Optimizing secondary dex'es is not implemented yet");
+ results.addAll(
+ mInjector
+ .getSecondaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
+ if (hasCancelledResult.get()) {
+ return createResult.get();
+ }
}
if ((params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
@@ -166,6 +170,13 @@
}
@NonNull
+ SecondaryDexOptimizer getSecondaryDexOptimizer(@NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ return new SecondaryDexOptimizer(mContext, pkgState, pkg, params, cancellationSignal);
+ }
+
+ @NonNull
public AppHibernationManager getAppHibernationManager() {
return mContext.getSystemService(AppHibernationManager.class);
}
diff --git a/libartservice/service/java/com/android/server/art/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
index 078f698..8cfe8ad 100644
--- a/libartservice/service/java/com/android/server/art/DexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -86,11 +86,6 @@
public final List<DexContainerFileOptimizeResult> dexopt() throws RemoteException {
List<DexContainerFileOptimizeResult> results = new ArrayList<>();
- String targetCompilerFilter = adjustCompilerFilter(mParams.getCompilerFilter());
- if (targetCompilerFilter.equals(OptimizeParams.COMPILER_FILTER_NOOP)) {
- return results;
- }
-
for (DexInfoType dexInfo : getDexInfoList()) {
ProfilePath profile = null;
boolean succeeded = true;
@@ -99,7 +94,10 @@
continue;
}
- String compilerFilter = targetCompilerFilter;
+ String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
+ if (compilerFilter.equals(OptimizeParams.COMPILER_FILTER_NOOP)) {
+ continue;
+ }
boolean needsToBeShared = needsToBeShared(dexInfo);
boolean isOtherReadable = true;
@@ -138,7 +136,8 @@
DexFile.isProfileGuidedCompilerFilter(compilerFilter);
Utils.check(isProfileGuidedCompilerFilter == (profile != null));
- boolean canBePublic = !isProfileGuidedCompilerFilter || isOtherReadable;
+ boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
+ && isDexFilePublic(dexInfo);
Utils.check(Utils.implies(needsToBeShared, canBePublic));
PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
@@ -245,7 +244,8 @@
}
@NonNull
- private String adjustCompilerFilter(@NonNull String targetCompilerFilter) {
+ private String adjustCompilerFilter(
+ @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
String systemUiCompilerFilter = getSystemUiCompilerFilter();
if (!systemUiCompilerFilter.isEmpty()) {
@@ -264,6 +264,12 @@
return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
}
+ // We cannot do AOT compilation if we don't have a valid class loader context.
+ if (dexInfo.classLoaderContext() == null
+ && DexFile.isOptimizedCompilerFilter(targetCompilerFilter)) {
+ return "verify";
+ }
+
return targetCompilerFilter;
}
@@ -346,6 +352,9 @@
// The result should come from artd even if all the bits of `dexoptTrigger` are set
// because the result also contains information about the usable VDEX file.
+ // Note that the class loader context can be null. In that case, we intentionally pass the
+ // null value down to lower levels to indicate that the class loader context check should be
+ // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
target.compilerFilter(), dexoptTrigger);
@@ -470,12 +479,21 @@
@NonNull protected abstract List<DexInfoType> getDexInfoList();
/** Returns true if the given dex file should be optimized. */
- protected abstract boolean isOptimizable(@NonNull DexInfoType dexInfo) throws RemoteException;
+ protected abstract boolean isOptimizable(@NonNull DexInfoType dexInfo);
- /** Returns true if the artifacts should be shared with other apps. */
+ /**
+ * Returns true if the artifacts should be shared with other apps. Note that this must imply
+ * {@link #isDexFilePublic(DexInfoType)}.
+ */
protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
/**
+ * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+ * (S_IROTH).
+ */
+ protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
+
+ /**
* Returns a reference profile initialized from an external profile (e.g., a DM profile) if
* one exists, or null otherwise.
*/
@@ -552,30 +570,30 @@
*
* @hide
*/
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
public static class Injector {
@NonNull private final Context mContext;
- Injector(@NonNull Context context) {
+ public Injector(@NonNull Context context) {
mContext = context;
}
- boolean isSystemUiPackage(@NonNull String packageName) {
+ public boolean isSystemUiPackage(@NonNull String packageName) {
return packageName.equals(mContext.getString(R.string.config_systemUi));
}
@NonNull
- UserManager getUserManager() {
+ public UserManager getUserManager() {
return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
}
@NonNull
- DexUseManager getDexUseManager() {
+ public DexUseManager getDexUseManager() {
return DexUseManager.getInstance();
}
@NonNull
- IArtd getArtd() {
+ public IArtd getArtd() {
return Utils.getArtd();
}
}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManager.java b/libartservice/service/java/com/android/server/art/DexUseManager.java
index 9cbca54..e51c3bf 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManager.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManager.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.util.Log;
@@ -72,6 +74,8 @@
@GuardedBy("DexUseManager.class") @Nullable private static DexUseManager sInstance = null;
+ @NonNull private final Injector mInjector;
+
@GuardedBy("this") @NonNull private DexUse mDexUse = new DexUse();
@NonNull
@@ -82,6 +86,15 @@
return sInstance;
}
+ private DexUseManager() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ public DexUseManager(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
/** Returns all entities that load the given primary dex file owned by the given package. */
@VisibleForTesting
@NonNull
@@ -104,22 +117,65 @@
return isUsedByOtherApps(getPrimaryDexLoaders(packageName, dexPath), packageName);
}
- /** Returns information about all secondary dex files owned by the given package. */
- public synchronized @NonNull List<SecondaryDexInfo> getSecondaryDexInfo(
+ /**
+ * Returns the basic information about all secondary dex files owned by the given package. This
+ * method doesn't take dex file visibility into account, so it can only be used for debugging
+ * purpose, such as dumpsys.
+ *
+ * @see #getFilteredDetailedSecondaryDexInfo(String)
+ */
+ public @NonNull List<? extends SecondaryDexInfo> getSecondaryDexInfo(
@NonNull String packageName) {
+ return getSecondaryDexInfoImpl(packageName, false /* checkDexFile */);
+ }
+
+ /**
+ * Same as above, but requires disk IO, and returns the detailed information, including dex file
+ * visibility, filtered by dex file existence and visibility.
+ */
+ public @NonNull List<DetailedSecondaryDexInfo> getFilteredDetailedSecondaryDexInfo(
+ @NonNull String packageName) {
+ return getSecondaryDexInfoImpl(packageName, true /* checkDexFile */);
+ }
+
+ /**
+ * @param checkDexFile if true, check the existence and visibility of the dex files, and filter
+ * the results accordingly. Note that the value of the {@link
+ * DetailedSecondaryDexInfo#isDexFilePublic()} field is undefined if this argument is
+ * false.
+ */
+ private synchronized @NonNull List<DetailedSecondaryDexInfo> getSecondaryDexInfoImpl(
+ @NonNull String packageName, boolean checkDexFile) {
PackageDexUse packageDexUse = mDexUse.mPackageDexUseByOwningPackageName.get(packageName);
if (packageDexUse == null) {
return List.of();
}
- var results = new ArrayList<SecondaryDexInfo>();
+ var results = new ArrayList<DetailedSecondaryDexInfo>();
for (var entry : packageDexUse.mSecondaryDexUseByDexFile.entrySet()) {
String dexPath = entry.getKey();
SecondaryDexUse secondaryDexUse = entry.getValue();
- if (secondaryDexUse.mRecordByLoader.isEmpty()) {
+
+ @FileVisibility
+ int visibility =
+ checkDexFile ? getDexFileVisibility(dexPath) : FileVisibility.OTHER_READABLE;
+ if (visibility == FileVisibility.NOT_FOUND) {
+ continue;
+ }
+
+ Map<DexLoader, SecondaryDexUseRecord> filteredRecordByLoader;
+ if (visibility == FileVisibility.OTHER_READABLE) {
+ filteredRecordByLoader = secondaryDexUse.mRecordByLoader;
+ } else {
+ // Only keep the entry that belongs to the same app.
+ DexLoader sameApp = DexLoader.create(packageName, false /* isolatedProcess */);
+ SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.get(sameApp);
+ filteredRecordByLoader = record != null ? Map.of(sameApp, record) : Map.of();
+ }
+ if (filteredRecordByLoader.isEmpty()) {
continue;
}
List<String> distinctClcList =
- secondaryDexUse.mRecordByLoader.values()
+ filteredRecordByLoader.values()
.stream()
.map(record -> Utils.assertNonEmpty(record.mClassLoaderContext))
.filter(clc
@@ -140,14 +196,15 @@
// need to take apps with unsupported CLCs into account because the vdex file is still
// usable to them.
Set<String> distinctAbiNames =
- secondaryDexUse.mRecordByLoader.values()
+ filteredRecordByLoader.values()
.stream()
.map(record -> Utils.assertNonEmpty(record.mAbiName))
.collect(Collectors.toSet());
- Set<DexLoader> loaders = Set.copyOf(secondaryDexUse.mRecordByLoader.keySet());
- results.add(SecondaryDexInfo.create(dexPath,
+ Set<DexLoader> loaders = Set.copyOf(filteredRecordByLoader.keySet());
+ results.add(DetailedSecondaryDexInfo.create(dexPath,
Objects.requireNonNull(secondaryDexUse.mUserHandle), clc, distinctAbiNames,
- loaders, isUsedByOtherApps(loaders, packageName)));
+ loaders, isUsedByOtherApps(loaders, packageName),
+ visibility == FileVisibility.OTHER_READABLE));
}
return Collections.unmodifiableList(results);
}
@@ -338,13 +395,21 @@
// TODO(b/253570365): Make the validation more strict.
}
+ private @FileVisibility int getDexFileVisibility(@NonNull String dexPath) {
+ try {
+ return mInjector.getArtd().getDexFileVisibility(dexPath);
+ } catch (ServiceSpecificException | RemoteException e) {
+ Log.e(TAG, "Failed to get visibility of " + dexPath, e);
+ return FileVisibility.NOT_FOUND;
+ }
+ }
+
/**
- * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
+ * Basic information about a secondary dex file (an APK or JAR file that an app adds to its
* own data directory and loads dynamically).
*/
@Immutable
- @AutoValue
- public abstract static class SecondaryDexInfo implements DetailedDexInfo {
+ public abstract static class SecondaryDexInfo {
// Special encoding used to denote a foreign ClassLoader was found when trying to encode
// class loader contexts for each classpath element in a ClassLoader.
// Must be in sync with `kUnsupportedClassLoaderContextEncoding` in
@@ -358,14 +423,6 @@
@VisibleForTesting
public static final String VARYING_CLASS_LOADER_CONTEXTS = "=VaryingClassLoaderContexts=";
- static SecondaryDexInfo create(@NonNull String dexPath, @NonNull UserHandle userHandle,
- @Nullable String classLoaderContext, @NonNull Set<String> abiNames,
- @NonNull Set<DexLoader> loaders, boolean isUsedByOtherApps) {
- return new AutoValue_DexUseManager_SecondaryDexInfo(dexPath, userHandle,
- classLoaderContext, Collections.unmodifiableSet(abiNames),
- Collections.unmodifiableSet(loaders), isUsedByOtherApps);
- }
-
/** The absolute path to the dex file within the user's app data directory. */
public abstract @NonNull String dexPath();
@@ -379,24 +436,52 @@
* A string describing the structure of the class loader that the dex file is loaded with,
* or {@link #UNSUPPORTED_CLASS_LOADER_CONTEXT} or {@link #VARYING_CLASS_LOADER_CONTEXTS}.
*/
- public abstract @NonNull String classLoaderContext();
+ public abstract @NonNull String displayClassLoaderContext();
- /** The set of ABIs of the dex file is loaded with. */
+ /**
+ * A string describing the structure of the class loader that the dex file is loaded with,
+ * or null if the class loader context is invalid.
+ */
+ public @Nullable String classLoaderContext() {
+ return !displayClassLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
+ && !displayClassLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS)
+ ? displayClassLoaderContext()
+ : null;
+ }
+
+ /** The set of ABIs of the dex file is loaded with. Guaranteed to be non-empty. */
public abstract @NonNull Set<String> abiNames();
- /** The set of entities that load the dex file. */
+ /** The set of entities that load the dex file. Guaranteed to be non-empty. */
public abstract @NonNull Set<DexLoader> loaders();
/** Returns whether the dex file is used by apps other than the app that owns it. */
public abstract boolean isUsedByOtherApps();
+ }
+
+ /**
+ * Detailed information about a secondary dex file (an APK or JAR file that an app adds to its
+ * own data directory and loads dynamically). It contains the visibility of the dex file in
+ * addition to what is in {@link SecondaryDexInfo}, but producing it requires disk IO.
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class DetailedSecondaryDexInfo
+ extends SecondaryDexInfo implements DetailedDexInfo {
+ static DetailedSecondaryDexInfo create(@NonNull String dexPath,
+ @NonNull UserHandle userHandle, @NonNull String displayClassLoaderContext,
+ @NonNull Set<String> abiNames, @NonNull Set<DexLoader> loaders,
+ boolean isUsedByOtherApps, boolean isDexFilePublic) {
+ return new AutoValue_DexUseManager_DetailedSecondaryDexInfo(dexPath, userHandle,
+ displayClassLoaderContext, Collections.unmodifiableSet(abiNames),
+ Collections.unmodifiableSet(loaders), isUsedByOtherApps, isDexFilePublic);
+ }
/**
- * Returns true if the class loader context is suitable for compilation.
+ * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
+ * (S_IROTH).
*/
- public boolean isClassLoaderContextValid() {
- return !classLoaderContext().equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)
- && !classLoaderContext().equals(VARYING_CLASS_LOADER_CONTEXTS);
- }
+ public abstract boolean isDexFilePublic();
}
private static class DexUse {
@@ -543,4 +628,17 @@
mAbiName = Utils.assertNonEmpty(proto.getAbiName());
}
}
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull
+ public IArtd getArtd() {
+ return Utils.getArtd();
+ }
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index ea1b20a..032f2ae 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -98,6 +98,13 @@
}
@Override
+ protected boolean isDexFilePublic(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ // The filesystem permission of a primary dex file always has the S_IROTH bit. In practice,
+ // the accessibility is enforced by Application Sandbox, not filesystem permission.
+ return true;
+ }
+
+ @Override
@Nullable
protected ProfilePath initReferenceProfile(@NonNull DetailedPrimaryDexInfo dexInfo)
throws RemoteException {
@@ -170,6 +177,7 @@
protected boolean isAppImageAllowed() {
// Disable app images if the app requests for the splits to be loaded in isolation because
// app images are unsupported for multiple class loaders (b/72696798).
+ // TODO(jiakaiz): Investigate whether this is still the best choice today.
return !PrimaryDexUtils.isIsolatedSplitLoading(mPkg);
}
@@ -178,8 +186,8 @@
protected OutputProfile buildOutputProfile(
@NonNull DetailedPrimaryDexInfo dexInfo, boolean isPublic) {
String profileName = getProfileName(dexInfo.splitName());
- return AidlUtils.buildOutputProfileForPrimary(mPkgState.getPackageName(), profileName,
- mPkgState.getAppId(), mSharedGid, isPublic);
+ return AidlUtils.buildOutputProfileForPrimary(
+ mPkgState.getPackageName(), profileName, Process.SYSTEM_UID, mSharedGid, isPublic);
}
@Override
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
new file mode 100644
index 0000000..95b9ddb
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexOptimizer.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManager.DetailedSecondaryDexInfo;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.OutputArtifacts.PermissionSettings.SeContext;
+import static com.android.server.art.Utils.Abi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.CancellationSignal;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.List;
+
+/** @hide */
+public class SecondaryDexOptimizer extends DexOptimizer<DetailedSecondaryDexInfo> {
+ private static final String TAG = "SecondaryDexOptimizer";
+
+ public SecondaryDexOptimizer(@NonNull Context context, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ this(new Injector(context), pkgState, pkg, params, cancellationSignal);
+ }
+
+ @VisibleForTesting
+ public SecondaryDexOptimizer(@NonNull Injector injector, @NonNull PackageState pkgState,
+ @NonNull AndroidPackage pkg, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal) {
+ super(injector, pkgState, pkg, params, cancellationSignal);
+ }
+
+ @Override
+ protected boolean isInDalvikCache() {
+ // A secondary dex file is added by the app, so it's always in a writable location and hence
+ // never uses dalvik-cache.
+ return false;
+ }
+
+ @Override
+ @NonNull
+ protected List<DetailedSecondaryDexInfo> getDexInfoList() {
+ return mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo(
+ mPkgState.getPackageName());
+ }
+
+ @Override
+ protected boolean isOptimizable(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return true;
+ }
+
+ @Override
+ protected boolean needsToBeShared(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return dexInfo.isUsedByOtherApps();
+ }
+
+ @Override
+ protected boolean isDexFilePublic(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return dexInfo.isDexFilePublic();
+ }
+
+ @Override
+ @Nullable
+ protected ProfilePath initReferenceProfile(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ // A secondary dex file doesn't have any external profile to use.
+ return null;
+ }
+
+ @Override
+ @NonNull
+ protected PermissionSettings getPermissionSettings(
+ @NonNull DetailedSecondaryDexInfo dexInfo, boolean canBePublic) {
+ int uid = getUid(dexInfo);
+ // We don't need the "read" bit for "others" on the directories because others
+ // only need to access the files in the directories, but they don't need to "ls"
+ // the directories.
+ FsPermission dirFsPermission = AidlUtils.buildFsPermission(
+ uid, uid, false /* isOtherReadable */, canBePublic /* isOtherExecutable */);
+ FsPermission fileFsPermission = AidlUtils.buildFsPermission(uid, uid, canBePublic);
+ SeContext seContext = AidlUtils.buildSeContext(
+ new com.android.server.art.wrapper.PackageState(mPkgState).getSeInfo(), uid);
+ return AidlUtils.buildPermissionSettings(dirFsPermission, fileFsPermission, seContext);
+ }
+
+ @Override
+ @NonNull
+ protected List<Abi> getAllAbis(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return Utils.getAllAbisForNames(dexInfo.abiNames(), mPkgState);
+ }
+
+ @Override
+ @NonNull
+ protected ProfilePath buildRefProfilePath(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath());
+ }
+
+ @Override
+ protected boolean isAppImageAllowed() {
+ // The runtime can only load the app image of the base APK.
+ return false;
+ }
+
+ @Override
+ @NonNull
+ protected OutputProfile buildOutputProfile(
+ @NonNull DetailedSecondaryDexInfo dexInfo, boolean isPublic) {
+ int uid = getUid(dexInfo);
+ return AidlUtils.buildOutputProfileForSecondary(dexInfo.dexPath(), uid, uid, isPublic);
+ }
+
+ @Override
+ @NonNull
+ protected List<ProfilePath> getCurProfiles(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ // A secondary dex file can only be loaded by one user, so there is only one profile.
+ return List.of(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath()));
+ }
+
+ private int getUid(@NonNull DetailedSecondaryDexInfo dexInfo) {
+ return dexInfo.userHandle().getUid(mPkgState.getAppId());
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 2bd642a..8136b0a 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -36,6 +36,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
/** @hide */
public final class Utils {
@@ -62,6 +64,7 @@
return array == null || array.length == 0;
}
+ /** Returns the ABI information for the package. */
@NonNull
public static List<Abi> getAllAbis(@NonNull PackageState pkgState) {
List<Abi> abis = new ArrayList<>();
@@ -82,6 +85,18 @@
return abis;
}
+ /** Returns the ABI information for the ABIs with the given names. */
+ @NonNull
+ public static List<Abi> getAllAbisForNames(
+ @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
+ Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
+ return abiNames.stream()
+ .map(name
+ -> Abi.create(name, VMRuntime.getInstructionSet(name),
+ name.equals(pkgPrimaryAbi.name())))
+ .collect(Collectors.toList());
+ }
+
@NonNull
public static Abi getPrimaryAbi(@NonNull PackageState pkgState) {
String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
diff --git a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
index 0f9a20e..932813f 100644
--- a/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
+++ b/libartservice/service/java/com/android/server/art/model/DetailedDexInfo.java
@@ -17,6 +17,7 @@
package com.android.server.art.model;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import com.android.internal.annotations.Immutable;
@@ -31,7 +32,8 @@
@NonNull String dexPath();
/**
- * A string describing the structure of the class loader that the dex file is loaded with.
+ * A string describing the structure of the class loader that the dex file is loaded with, or
+ * null if the class loader context is invalid.
*/
- @NonNull String classLoaderContext();
+ @Nullable String classLoaderContext();
}
diff --git a/libartservice/service/java/com/android/server/art/wrapper/PackageState.java b/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
index fafe8ca..3f1e871 100644
--- a/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
+++ b/libartservice/service/java/com/android/server/art/wrapper/PackageState.java
@@ -19,12 +19,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.server.pm.pkg.AndroidPackage;
/** @hide */
public class PackageState {
- @NonNull private final Object mPkgState;
+ @NonNull private final com.android.server.pm.pkg.PackageState mPkgState;
- public PackageState(@NonNull Object pkgState) {
+ public PackageState(@NonNull com.android.server.pm.pkg.PackageState pkgState) {
mPkgState = pkgState;
}
@@ -36,4 +39,32 @@
throw new RuntimeException(e);
}
}
+
+ @Nullable
+ public String getSeInfo() {
+ try {
+ Object pkgStateUnserialized =
+ mPkgState.getClass().getMethod("getTransientState").invoke(mPkgState);
+ String seInfo = (String) pkgStateUnserialized.getClass()
+ .getMethod("getOverrideSeInfo")
+ .invoke(pkgStateUnserialized);
+ if (!TextUtils.isEmpty(seInfo)) {
+ return seInfo;
+ }
+
+ // Default to the information in `AndroidPackage`. The defaulting behavior will
+ // eventually be done by `PackageState` internally.
+ AndroidPackage pkg = mPkgState.getAndroidPackage();
+ if (pkg == null) {
+ // This should never happen because we check the existence of the package at the
+ // beginning of each ART Services method.
+ throw new IllegalStateException("Unable to get package "
+ + mPkgState.getPackageName() + ". This should never happen.");
+ }
+
+ return (String) pkg.getClass().getMethod("getSeInfo").invoke(pkg);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
index aebc502..f83d1bc 100644
--- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -16,6 +16,7 @@
package com.android.server.art;
+import static com.android.server.art.DexUseManager.DetailedSecondaryDexInfo;
import static com.android.server.art.DexUseManager.DexLoader;
import static com.android.server.art.DexUseManager.SecondaryDexInfo;
@@ -79,8 +80,17 @@
private final UserHandle mUserHandle = Binder.getCallingUserHandle();
+ /**
+ * The default value of `isDexFilePublic` returned by `getSecondaryDexInfo`. The value doesn't
+ * matter because it's undefined, but it's needed for deep equality check, to make the test
+ * simpler.
+ */
+ private final boolean mDefaultIsDexFilePublic = true;
+
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
- private DexUseManager mDexUseManager = DexUseManager.getInstance();
+ @Mock private DexUseManager.Injector mInjector;
+ @Mock private IArtd mArtd;
+ private DexUseManager mDexUseManager;
private String mCeDir;
private String mDeDir;
@@ -129,7 +139,9 @@
Binder.getCallingUserHandle().getIdentifier(), OWNING_PKG_NAME)
.toString();
- mDexUseManager.clear();
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+
+ mDexUseManager = new DexUseManager(mInjector);
}
@Test
@@ -236,13 +248,14 @@
public void testSecondaryDexOwned() {
mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, "CLC",
- Set.of("arm64-v8a"),
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a"),
Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
- false /* isUsedByOtherApps */));
- assertThat(dexInfoList.get(0).isClassLoaderContextValid()).isTrue();
+ false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
}
@Test
@@ -250,26 +263,28 @@
when(Process.isIsolated(anyInt())).thenReturn(true);
mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mDeDir + "/foo.apk", "CLC"));
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mDeDir + "/foo.apk", mUserHandle, "CLC",
- Set.of("arm64-v8a"),
+ .containsExactly(DetailedSecondaryDexInfo.create(mDeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a"),
Set.of(DexLoader.create(OWNING_PKG_NAME, true /* isolatedProcess */)),
- true /* isUsedByOtherApps */));
- assertThat(dexInfoList.get(0).isClassLoaderContextValid()).isTrue();
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
}
@Test
public void testSecondaryDexOthers() {
mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle, "CLC",
- Set.of("armeabi-v7a"),
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("armeabi-v7a"),
Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
- true /* isUsedByOtherApps */));
- assertThat(dexInfoList.get(0).isClassLoaderContextValid()).isTrue();
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ assertThat(dexInfoList.get(0).classLoaderContext()).isEqualTo("CLC");
}
@Test
@@ -277,13 +292,14 @@
mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME,
Map.of(mCeDir + "/foo.apk", SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT));
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT, Set.of("armeabi-v7a"),
Set.of(DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
- true /* isUsedByOtherApps */));
- assertThat(dexInfoList.get(0).isClassLoaderContextValid()).isFalse();
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
}
@Test
@@ -291,15 +307,16 @@
mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC2"));
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
Set.of("arm64-v8a", "armeabi-v7a"),
Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
- true /* isUsedByOtherApps */));
- assertThat(dexInfoList.get(0).isClassLoaderContextValid()).isFalse();
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ assertThat(dexInfoList.get(0).classLoaderContext()).isNull();
}
/** Checks that it ignores and dedups things correctly. */
@@ -350,9 +367,10 @@
mDexUseManager.load(tempFile.getPath());
}
- List<SecondaryDexInfo> dexInfoList = mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
+ List<? extends SecondaryDexInfo> dexInfoList =
+ mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME);
assertThat(dexInfoList)
- .containsExactly(SecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
"UpdatedCLC", Set.of("arm64-v8a", "armeabi-v7a"),
Set.of(DexLoader.create(OWNING_PKG_NAME,
false /* isolatedProcess */),
@@ -360,21 +378,77 @@
true /* isolatedProcess */),
DexLoader.create(LOADING_PKG_NAME,
false /* isolatedProcess */)),
- true /* isUsedByOtherApps */),
- SecondaryDexInfo.create(mCeDir + "/bar.apk", mUserHandle,
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+ DetailedSecondaryDexInfo.create(mCeDir + "/bar.apk", mUserHandle,
SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS,
Set.of("arm64-v8a", "armeabi-v7a"),
Set.of(DexLoader.create(
OWNING_PKG_NAME, false /* isolatedProcess */),
DexLoader.create(
LOADING_PKG_NAME, false /* isolatedProcess */)),
- true /* isUsedByOtherApps */),
- SecondaryDexInfo.create(mCeDir + "/baz.apk", mUserHandle,
+ true /* isUsedByOtherApps */, mDefaultIsDexFilePublic),
+ DetailedSecondaryDexInfo.create(mCeDir + "/baz.apk", mUserHandle,
SecondaryDexInfo.UNSUPPORTED_CLASS_LOADER_CONTEXT,
Set.of("arm64-v8a"),
Set.of(DexLoader.create(
OWNING_PKG_NAME, false /* isolatedProcess */)),
- false /* isUsedByOtherApps */));
+ false /* isUsedByOtherApps */, mDefaultIsDexFilePublic));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexPublic() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+ mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a", "armeabi-v7a"),
+ Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */),
+ DexLoader.create(LOADING_PKG_NAME, false /* isolatedProcess */)),
+ true /* isUsedByOtherApps */, true /* isDexFilePublic */));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexPrivate() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+ mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ when(Process.isIsolated(anyInt())).thenReturn(true);
+ mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME))
+ .containsExactly(DetailedSecondaryDexInfo.create(mCeDir + "/foo.apk", mUserHandle,
+ "CLC", Set.of("arm64-v8a"),
+ Set.of(DexLoader.create(OWNING_PKG_NAME, false /* isolatedProcess */)),
+ false /* isUsedByOtherApps */, false /* isDexFilePublic */));
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexFilteredDueToVisibility() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk"))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ mDexUseManager.addDexUse(mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ when(Process.isIsolated(anyInt())).thenReturn(true);
+ mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
+ }
+
+ @Test
+ public void testFilteredDetailedSecondaryDexFilteredDueToNotFound() throws Exception {
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
+
+ mDexUseManager.addDexUse(mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+
+ assertThat(mDexUseManager.getFilteredDetailedSecondaryDexInfo(OWNING_PKG_NAME)).isEmpty();
}
@Test(expected = IllegalArgumentException.class)
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 2faa59d..5b9264d 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.Process;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
@@ -73,9 +74,9 @@
private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
- PKG_NAME, "primary", UID, SHARED_GID, true /* isOtherReadable */);
+ PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
- PKG_NAME, "primary", UID, SHARED_GID, false /* isOtherReadable */);
+ PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */);
private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
private final ProfilePath mSplit0RefProfile =
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
new file mode 100644
index 0000000..7b967e4
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import static com.android.server.art.DexUseManager.DetailedSecondaryDexInfo;
+import static com.android.server.art.DexUseManager.SecondaryDexInfo;
+import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
+import static com.android.server.art.OutputArtifacts.PermissionSettings;
+import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult;
+import static com.android.server.art.testing.TestingUtils.deepEq;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.CancellationSignal;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.OptimizeParams;
+import com.android.server.art.model.OptimizeResult;
+import com.android.server.art.testing.StaticMockitoRule;
+import com.android.server.art.testing.TestingUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateUnserialized;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecondaryDexOptimizerTest {
+ private static final String PKG_NAME = "com.example.foo";
+ private static final int APP_ID = 12345;
+ private static final UserHandle USER_HANDLE = UserHandle.of(2);
+ private static final int UID = USER_HANDLE.getUid(APP_ID);
+ private static final String APP_DATA_DIR = "/data/user/2/" + PKG_NAME;
+ private static final String DEX_1 = APP_DATA_DIR + "/1.apk";
+ private static final String DEX_2 = APP_DATA_DIR + "/2.apk";
+ private static final String DEX_3 = APP_DATA_DIR + "/3.apk";
+
+ private final OptimizeParams mOptimizeParams =
+ new OptimizeParams.Builder("bg-dexopt")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX, ArtFlags.FLAG_FOR_SECONDARY_DEX)
+ .build();
+
+ private final ProfilePath mDex1RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_1);
+ private final ProfilePath mDex1CurProfile = AidlUtils.buildProfilePathForSecondaryCur(DEX_1);
+ private final ProfilePath mDex2RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_2);
+ private final ProfilePath mDex3RefProfile = AidlUtils.buildProfilePathForSecondaryRef(DEX_3);
+ private final OutputProfile mDex1PrivateOutputProfile =
+ AidlUtils.buildOutputProfileForSecondary(DEX_1, UID, UID, false /* isOtherReadable */);
+
+ private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+ private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
+ | DexoptTrigger.COMPILER_FILTER_IS_SAME
+ | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE;
+
+ @Rule
+ public StaticMockitoRule mockitoRule =
+ new StaticMockitoRule(SystemProperties.class, Constants.class);
+
+ @Mock private SecondaryDexOptimizer.Injector mInjector;
+ @Mock private IArtd mArtd;
+ @Mock private DexUseManager mDexUseManager;
+ private PackageState mPkgState;
+ private AndroidPackage mPkg;
+ private CancellationSignal mCancellationSignal;
+
+ private SecondaryDexOptimizer mSecondaryDexOptimizer;
+
+ @Before
+ public void setUp() throws Exception {
+ lenient()
+ .when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
+ .thenReturn(false);
+ lenient().when(SystemProperties.get("dalvik.vm.appimageformat")).thenReturn("lz4");
+ lenient().when(SystemProperties.get("pm.dexopt.shared")).thenReturn("speed");
+
+ // No ISA translation.
+ lenient()
+ .when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
+ .thenReturn("");
+
+ lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
+ lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
+
+ lenient().when(mInjector.getArtd()).thenReturn(mArtd);
+ lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(false);
+ lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager);
+
+ List<DetailedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo();
+ lenient()
+ .when(mDexUseManager.getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME)))
+ .thenReturn(secondaryDexInfo);
+
+ mPkgState = createPackageState();
+ mPkg = mPkgState.getAndroidPackage();
+ mCancellationSignal = new CancellationSignal();
+
+ prepareProfiles();
+
+ // Dexopt is always needed and successful.
+ lenient()
+ .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
+ .thenReturn(dexoptIsNeeded());
+ lenient()
+ .when(mArtd.dexopt(
+ any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), any()))
+ .thenReturn(createDexoptResult());
+
+ lenient()
+ .when(mArtd.createCancellationSignal())
+ .thenReturn(mock(IArtdCancellationSignal.class));
+
+ mSecondaryDexOptimizer = new SecondaryDexOptimizer(
+ mInjector, mPkgState, mPkg, mOptimizeParams, mCancellationSignal);
+ }
+
+ @Test
+ public void testDexopt() throws Exception {
+ assertThat(mSecondaryDexOptimizer.dexopt())
+ .comparingElementsUsing(TestingUtils.<DexContainerFileOptimizeResult>deepEquality())
+ .containsExactly(
+ new DexContainerFileOptimizeResult(DEX_1, true /* isPrimaryAbi */,
+ "arm64-v8a", "speed-profile", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+ new DexContainerFileOptimizeResult(DEX_2, true /* isPrimaryAbi */,
+ "arm64-v8a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+ new DexContainerFileOptimizeResult(DEX_2, false /* isPrimaryAbi */,
+ "armeabi-v7a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+ new DexContainerFileOptimizeResult(DEX_3, true /* isPrimaryAbi */,
+ "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
+ 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */));
+
+ // It should use profile for dex 1.
+
+ verify(mArtd).mergeProfiles(deepEq(List.of(mDex1CurProfile)), deepEq(mDex1RefProfile),
+ deepEq(mDex1PrivateOutputProfile), eq(DEX_1));
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_1), eq("arm64"), any(), eq("speed-profile"), eq(mBetterOrSameDexoptTrigger));
+ checkDexoptWithPrivateProfile(verify(mArtd), DEX_1, "arm64",
+ ProfilePath.tmpProfilePath(mDex1PrivateOutputProfile.profilePath), "CLC_FOR_DEX_1");
+
+ verify(mArtd).commitTmpProfile(deepEq(mDex1PrivateOutputProfile.profilePath));
+
+ verify(mArtd).deleteProfile(deepEq(mDex1CurProfile));
+
+ // It should use "speed" for dex 2 for both ISAs and make the artifacts public.
+
+ verify(mArtd, never()).isProfileUsable(deepEq(mDex2RefProfile), any());
+ verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex2RefProfile), any(), any());
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_2), eq("arm64"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(
+ verify(mArtd), DEX_2, "arm64", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_2), eq("arm"), any(), eq("speed"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(
+ verify(mArtd), DEX_2, "arm", "speed", "CLC_FOR_DEX_2", true /* isPublic */);
+
+ // It should use "verify" for dex 3 and make the artifacts private.
+
+ verify(mArtd, never()).isProfileUsable(deepEq(mDex3RefProfile), any());
+ verify(mArtd, never()).mergeProfiles(any(), deepEq(mDex3RefProfile), any(), any());
+
+ verify(mArtd).getDexoptNeeded(
+ eq(DEX_3), eq("arm64"), isNull(), eq("verify"), eq(mDefaultDexoptTrigger));
+ checkDexoptWithNoProfile(verify(mArtd), DEX_3, "arm64", "verify",
+ null /* classLoaderContext */, false /* isPublic */);
+ }
+
+ private AndroidPackage createPackage() {
+ var pkg = mock(AndroidPackage.class);
+ lenient().when(pkg.isVmSafeMode()).thenReturn(false);
+ lenient().when(pkg.isDebuggable()).thenReturn(false);
+ lenient().when(pkg.getTargetSdkVersion()).thenReturn(123);
+ lenient().when(pkg.isSignedWithPlatformKey()).thenReturn(false);
+ lenient().when(pkg.isUsesNonSdkApi()).thenReturn(false);
+ return pkg;
+ }
+
+ private PackageState createPackageState() {
+ // TODO(b/254029037): Change PackageSetting to PackageState.
+ var pkgState = mock(PackageSetting.class);
+ lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+ lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn("arm64-v8a");
+ lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn("armeabi-v7a");
+ lenient().when(pkgState.getAppId()).thenReturn(APP_ID);
+ AndroidPackage pkg = createPackage();
+ lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
+
+ // TODO(b/254029037): Mock the real API instead of the hidden API.
+ var transientState = mock(PackageStateUnserialized.class);
+ lenient().when(transientState.getOverrideSeInfo()).thenReturn("se-info");
+ lenient().when(pkgState.getTransientState()).thenReturn(transientState);
+
+ return pkgState;
+ }
+
+ private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception {
+ // This should be compiled with profile.
+ var dex1Info = mock(DetailedSecondaryDexInfo.class);
+ lenient().when(dex1Info.dexPath()).thenReturn(DEX_1);
+ lenient().when(dex1Info.userHandle()).thenReturn(USER_HANDLE);
+ lenient().when(dex1Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_1");
+ lenient().when(dex1Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+ lenient().when(dex1Info.isUsedByOtherApps()).thenReturn(false);
+ lenient().when(dex1Info.isDexFilePublic()).thenReturn(true);
+
+ // This should be compiled without profile because it's used by other apps.
+ var dex2Info = mock(DetailedSecondaryDexInfo.class);
+ lenient().when(dex2Info.dexPath()).thenReturn(DEX_2);
+ lenient().when(dex2Info.userHandle()).thenReturn(USER_HANDLE);
+ lenient().when(dex2Info.classLoaderContext()).thenReturn("CLC_FOR_DEX_2");
+ lenient().when(dex2Info.abiNames()).thenReturn(Set.of("arm64-v8a", "armeabi-v7a"));
+ lenient().when(dex2Info.isUsedByOtherApps()).thenReturn(true);
+ lenient().when(dex2Info.isDexFilePublic()).thenReturn(true);
+
+ // This should be compiled with verify because the class loader context is invalid.
+ var dex3Info = mock(DetailedSecondaryDexInfo.class);
+ lenient().when(dex3Info.dexPath()).thenReturn(DEX_3);
+ lenient().when(dex3Info.userHandle()).thenReturn(USER_HANDLE);
+ lenient().when(dex3Info.classLoaderContext()).thenReturn(null);
+ lenient().when(dex3Info.abiNames()).thenReturn(Set.of("arm64-v8a"));
+ lenient().when(dex3Info.isUsedByOtherApps()).thenReturn(false);
+ lenient().when(dex3Info.isDexFilePublic()).thenReturn(false);
+
+ return List.of(dex1Info, dex2Info, dex3Info);
+ }
+
+ private void prepareProfiles() throws Exception {
+ // Profile for dex file 1 is usable.
+ lenient().when(mArtd.isProfileUsable(deepEq(mDex1RefProfile), any())).thenReturn(true);
+ lenient()
+ .when(mArtd.getProfileVisibility(deepEq(mDex1RefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ // Profiles for dex file 2 and 3 are also usable, but shouldn't be used.
+ lenient().when(mArtd.isProfileUsable(deepEq(mDex2RefProfile), any())).thenReturn(true);
+ lenient()
+ .when(mArtd.getProfileVisibility(deepEq(mDex2RefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+ lenient().when(mArtd.isProfileUsable(deepEq(mDex3RefProfile), any())).thenReturn(true);
+ lenient()
+ .when(mArtd.getProfileVisibility(deepEq(mDex3RefProfile)))
+ .thenReturn(FileVisibility.NOT_OTHER_READABLE);
+
+ lenient().when(mArtd.mergeProfiles(any(), any(), any(), any())).thenReturn(true);
+ }
+
+ private GetDexoptNeededResult dexoptIsNeeded() {
+ var result = new GetDexoptNeededResult();
+ result.isDexoptNeeded = true;
+ result.artifactsLocation = ArtifactsLocation.NONE_OR_ERROR;
+ result.isVdexUsable = false;
+ return result;
+ }
+
+ private DexoptResult createDexoptResult() {
+ var result = new DexoptResult();
+ result.cancelled = false;
+ result.wallTimeMs = 0;
+ result.cpuTimeMs = 0;
+ return result;
+ }
+
+ private void checkDexoptWithPrivateProfile(IArtd artd, String dexPath, String isa,
+ ProfilePath profile, String classLoaderContext) throws Exception {
+ PermissionSettings permissionSettings = buildPermissionSettings(false /* isPublic */);
+ OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+ dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+ artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+ eq("speed-profile"), deepEq(profile), any(), anyInt(),
+ argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+ }
+
+ private void checkDexoptWithNoProfile(IArtd artd, String dexPath, String isa,
+ String compilerFilter, String classLoaderContext, boolean isPublic) throws Exception {
+ PermissionSettings permissionSettings = buildPermissionSettings(isPublic);
+ OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(
+ dexPath, isa, false /* isInDalvikCache */, permissionSettings);
+ artd.dexopt(deepEq(outputArtifacts), eq(dexPath), eq(isa), eq(classLoaderContext),
+ eq(compilerFilter), isNull(), any(), anyInt(),
+ argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
+ }
+
+ private PermissionSettings buildPermissionSettings(boolean isPublic) {
+ return AidlUtils.buildPermissionSettings(
+ AidlUtils.buildFsPermission(UID, UID, false /* isOtherReadable */, isPublic),
+ AidlUtils.buildFsPermission(UID, UID, isPublic),
+ AidlUtils.buildSeContext("se-info", UID));
+ }
+}
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 7664fe5..a4ccdf8 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -617,6 +617,8 @@
return nullptr;
}
+ // The API doesn't support passing a class loader context, so skip the class loader context check
+ // and assume that it's OK.
OatFileAssistant oat_file_assistant(filename.c_str(),
target_instruction_set,
/* context= */ nullptr,
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 389479c..9e0c173 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -226,7 +226,7 @@
std::unique_ptr<OatFileAssistant> OatFileAssistant::Create(
const std::string& filename,
const std::string& isa_str,
- const std::string& context_str,
+ const std::optional<std::string>& context_str,
bool load_executable,
bool only_load_trusted_executable,
OatFileAssistantContext* ofa_context,
@@ -238,20 +238,23 @@
return nullptr;
}
- std::unique_ptr<ClassLoaderContext> tmp_context = ClassLoaderContext::Create(context_str.c_str());
- if (tmp_context == nullptr) {
- *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str.c_str());
- return nullptr;
- }
+ std::unique_ptr<ClassLoaderContext> tmp_context = nullptr;
+ if (context_str.has_value()) {
+ tmp_context = ClassLoaderContext::Create(context_str->c_str());
+ if (tmp_context == nullptr) {
+ *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str->c_str());
+ return nullptr;
+ }
- if (!tmp_context->OpenDexFiles(android::base::Dirname(filename.c_str()),
- /*context_fds=*/{},
- /*only_read_checksums=*/true)) {
- *error_msg =
- StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
- filename.c_str(),
- context_str.c_str());
- return nullptr;
+ if (!tmp_context->OpenDexFiles(android::base::Dirname(filename.c_str()),
+ /*context_fds=*/{},
+ /*only_read_checksums=*/true)) {
+ *error_msg =
+ StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
+ filename.c_str(),
+ context_str->c_str());
+ return nullptr;
+ }
}
auto assistant = std::make_unique<OatFileAssistant>(filename.c_str(),
@@ -1182,6 +1185,11 @@
}
bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const {
+ if (context_ == nullptr) {
+ // The caller requests to skip the check.
+ return true;
+ }
+
if (oat_file.IsBackedByVdexOnly()) {
// Only a vdex file, we don't depend on the class loader context.
return true;
@@ -1193,12 +1201,6 @@
return true;
}
- if (context_ == nullptr) {
- // When no class loader context is provided (which happens for deprecated
- // DexFile APIs), just assume it is OK.
- return true;
- }
-
ClassLoaderContext::VerificationResult matches = context_->VerifyClassLoaderContextMatch(
oat_file.GetClassLoaderContext(),
/*verify_names=*/ true,
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index ce069d2..2d0a150 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -146,6 +146,8 @@
// device. For example, on an arm device, use arm or arm64. An oat file can
// be loaded executable only if the ISA matches the current runtime.
//
+ // context should be the class loader context to check against, or null to skip the check.
+ //
// load_executable should be true if the caller intends to try and load
// executable code for this dex location.
//
@@ -182,7 +184,7 @@
static std::unique_ptr<OatFileAssistant> Create(
const std::string& filename,
const std::string& isa_str,
- const std::string& context_str,
+ const std::optional<std::string>& context_str,
bool load_executable,
bool only_load_trusted_executable,
OatFileAssistantContext* ofa_context,
@@ -523,6 +525,8 @@
std::string dex_location_;
+ // The class loader context to check against, or null representing that the check should be
+ // skipped.
ClassLoaderContext* context_;
// Whether or not the parent directory of the dex file is writable.
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 2904ecb..090532c 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -23,6 +23,7 @@
#include <functional>
#include <iterator>
#include <memory>
+#include <optional>
#include <string>
#include <type_traits>
#include <vector>
@@ -78,33 +79,40 @@
// Verify the static method (called from PM for dumpsys).
// This variant does not check class loader context.
if (!check_context) {
- std::string compilation_filter1;
- std::string compilation_reason1;
+ std::string compilation_filter;
+ std::string compilation_reason;
OatFileAssistant::GetOptimizationStatus(file,
kRuntimeISA,
- &compilation_filter1,
- &compilation_reason1,
+ &compilation_filter,
+ &compilation_reason,
MaybeGetOatFileAssistantContext());
- ASSERT_EQ(expected_filter_name, compilation_filter1);
- ASSERT_EQ(expected_reason, compilation_reason1);
+ ASSERT_EQ(expected_filter_name, compilation_filter);
+ ASSERT_EQ(expected_reason, compilation_reason);
}
// Verify the instance methods (called at runtime and from artd).
OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
+ VerifyOptimizationStatusWithInstance(
+ &assistant, expected_filter_name, expected_reason, expected_odex_status);
+ }
- std::string odex_location3; // ignored
- std::string compilation_filter3;
- std::string compilation_reason3;
- std::string odex_status3;
+ void VerifyOptimizationStatusWithInstance(OatFileAssistant* assistant,
+ const std::string& expected_filter,
+ const std::string& expected_reason,
+ const std::string& expected_odex_status) {
+ std::string odex_location; // ignored
+ std::string compilation_filter;
+ std::string compilation_reason;
+ std::string odex_status;
- assistant.GetOptimizationStatus(
- &odex_location3, &compilation_filter3, &compilation_reason3, &odex_status3);
+ assistant->GetOptimizationStatus(
+ &odex_location, &compilation_filter, &compilation_reason, &odex_status);
- ASSERT_EQ(expected_filter_name, compilation_filter3);
- ASSERT_EQ(expected_reason, compilation_reason3);
- ASSERT_EQ(expected_odex_status, odex_status3);
+ ASSERT_EQ(expected_filter, compilation_filter);
+ ASSERT_EQ(expected_reason, compilation_reason);
+ ASSERT_EQ(expected_odex_status, odex_status);
}
bool InsertNewBootClasspathEntry(const std::string& src, std::string* error_msg) {
@@ -2377,7 +2385,33 @@
ASSERT_NE(oat_file_assistant, nullptr);
// Verify that the created instance is usable.
- VerifyOptimizationStatus(dex_location, default_context_.get(), "speed", "install", "up-to-date");
+ VerifyOptimizationStatusWithInstance(oat_file_assistant.get(), "speed", "install", "up-to-date");
+}
+
+TEST_P(OatFileAssistantTest, CreateWithNullContext) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ std::unique_ptr<OatFileAssistant> oat_file_assistant =
+ OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ /*context_str=*/std::nullopt,
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeGetOatFileAssistantContext(),
+ &context,
+ &error_msg);
+ ASSERT_NE(oat_file_assistant, nullptr);
+ ASSERT_EQ(context, nullptr);
+
+ // Verify that the created instance is usable.
+ VerifyOptimizationStatusWithInstance(oat_file_assistant.get(), "speed", "install", "up-to-date");
}
TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) {