Add flag for clamping hidden api list
Allow clamping the hidden api list to a max value; if an API is
restricted for a newer SDK, transform it into a regular unsupported API.
This change is laying out the groundwork to ensure that older platforms
do not receive dex code updated that has unknown hidden api flags - e.g.
if the hidden api encode step uses '--max-hiddenapi-level=max-target-q',
then the resulting dex code will not exhibit undefined behavior with respect to
hidden api access when running on R devices, and any newer 'max-target-r' APIs
will be marked as 'unsupported'.
Test: mma test-art-host-gtest-art_hiddenapi_tests
Bug: 172453495
Merged-In: I710c440c8429081096d8a417f164c0e01c97ecd9
Change-Id: I710c440c8429081096d8a417f164c0e01c97ecd9
(cherry picked from commit a6b3b297bdd70ad3e915a48b02a7c7cf5f204e05)
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h
index 375bf88..3ece966 100644
--- a/libartbase/base/hiddenapi_flags.h
+++ b/libartbase/base/hiddenapi_flags.h
@@ -254,9 +254,26 @@
return true;
}
+ // Clamp a max-target-* up to the given maxSdk; if the given api list is higher than
+ // maxSdk, return unsupported instead.
+ static std::string CoerceAtMost(const std::string& name, const std::string& maxSdk) {
+ const auto apiListToClamp = FromName(name);
+ // If the api list is a domain instead, return it unmodified.
+ if (!apiListToClamp.IsValid()) {
+ return name;
+ }
+ const auto maxApiList = FromName(maxSdk);
+ CHECK(maxApiList.IsValid()) << "invalid api list name " << maxSdk;
+ if (apiListToClamp > maxApiList) {
+ return kValueNames[Unsupported().GetIntValue()];
+ }
+ return name;
+ }
+
bool operator==(const ApiList& other) const { return dex_flags_ == other.dex_flags_; }
bool operator!=(const ApiList& other) const { return !(*this == other); }
bool operator<(const ApiList& other) const { return dex_flags_ < other.dex_flags_; }
+ bool operator>(const ApiList& other) const { return dex_flags_ > other.dex_flags_; }
// Returns true if combining this ApiList with `other` will succeed.
bool CanCombineWith(const ApiList& other) const {
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index c249a7a..d1bb3fd 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -82,6 +82,11 @@
UsageError(" --api-flags=<filename>:");
UsageError(" CSV file with signatures of methods/fields and their respective flags");
UsageError("");
+ UsageError(" --max-hiddenapi-level=<max-target-*>:");
+ UsageError(" the maximum hidden api level for APIs. If an API was originally restricted");
+ UsageError(" to a newer sdk, turn it into a regular unsupported API instead.");
+ UsageError(" instead. The full list of valid values is in hiddenapi_flags.h");
+ UsageError("");
UsageError(" --no-force-assign-all:");
UsageError(" Disable check that all dex entries have been assigned a flag");
UsageError("");
@@ -908,6 +913,8 @@
api_flags_path_ = std::string(option.substr(strlen("--api-flags=")));
} else if (option == "--no-force-assign-all") {
force_assign_all_ = false;
+ } else if (StartsWith(option, "--max-hiddenapi-level=")) {
+ max_hiddenapi_level_ = std::string(option.substr(strlen("--max-hiddenapi-level=")));
} else {
Usage("Unknown argument '%s'", raw_option);
}
@@ -1022,12 +1029,23 @@
const std::string& signature = values[0];
+ // Skip signature
+ std::vector<std::string>::iterator apiListBegin = values.begin() + 1;
+ std::vector<std::string>::iterator apiListEnd = values.end();
+ if (!max_hiddenapi_level_.empty()) {
+ auto clamp_fn = [this](const std::string& apiListName) {
+ return ApiList::CoerceAtMost(apiListName,
+ max_hiddenapi_level_);
+ };
+ std::transform(apiListBegin, apiListEnd, apiListBegin, clamp_fn);
+ }
+
CHECK(api_flag_map.find(signature) == api_flag_map.end()) << path << ":" << line_number
<< ": Duplicate entry: " << signature << kErrorHelp;
ApiList membership;
- bool success = ApiList::FromNames(values.begin() + 1, values.end(), &membership);
+ bool success = ApiList::FromNames(apiListBegin, apiListEnd, &membership);
CHECK(success) << path << ":" << line_number
<< ": Some flags were not recognized: " << line << kErrorHelp;
CHECK(membership.IsValid()) << path << ":" << line_number
@@ -1156,6 +1174,9 @@
// Path to CSV file containing the list of API members and their flags.
// This could be both an input and output path.
std::string api_flags_path_;
+
+ // Override limit for sdk-max-* hidden APIs.
+ std::string max_hiddenapi_level_;
};
} // namespace hiddenapi
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 81e3f160..2593edd 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -66,8 +66,8 @@
std::vector<std::string> argv_str;
argv_str.push_back(GetHiddenApiCmd());
- argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end());
argv_str.push_back("encode");
+ argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end());
argv_str.push_back("--input-dex=" + in_dex.GetFilename());
argv_str.push_back("--output-dex=" + out_dex.GetFilename());
argv_str.push_back("--api-flags=" + flags_csv.GetFilename());
@@ -724,6 +724,60 @@
ASSERT_EQ(dex_file.get(), nullptr);
}
+TEST_F(HiddenApiTest, InstanceFieldMaxSdkHigherThanMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,max-target-r" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-q"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
+}
+
+TEST_F(HiddenApiTest, InstanceFieldMaxSdkEqualsMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,max-target-r" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::MaxTargetR(), GetIFieldHiddenFlags(*dex_file));
+}
+
+TEST_F(HiddenApiTest, InstanceFieldMaxSdkLowerThanMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,max-target-q" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::MaxTargetQ(), GetIFieldHiddenFlags(*dex_file));
+}
+
+TEST_F(HiddenApiTest, InstanceFieldBlockedUnchangedByMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,blocked" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetIFieldHiddenFlags(*dex_file));
+}
+
+TEST_F(HiddenApiTest, InstanceFieldUnsupportedUnchangedByMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,unsupported" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
+}
+
+TEST_F(HiddenApiTest, InstanceFieldSdkUnchangedByMaxHiddenApiLevel) {
+ ScratchFile dex, flags_csv;
+ OpenStream(flags_csv)
+ << "LMain;->ifield:I,sdk" << std::endl;
+ auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
+ ASSERT_NE(dex_file.get(), nullptr);
+ ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetIFieldHiddenFlags(*dex_file));
+}
+
// The following tests use this class hierarchy:
//
// AbstractPackageClass PublicInterface