hiddenapi: Fail if encoding S+ flags on R/Q libraries
The Q and R runtimes can handle Q/R flags but not S flags. Previously,
the hiddenapi encode command supported the --max-hiddenapi-level option
to allow the build to specify a maximum release flag that could be
encoded and to clamp the flags to that level but that option was not
actually used by the build. So, the option has been repurposed for this
change.
This change causes the encode command to fail if the option is supplied
and a flag to be encoded exceeds the maximum allowable level set by the
option.
Bug: 172453495
Test: m test-art-host-gtest-art_hiddenapi_tests
m droid && launch_cvd
Cherry pick changes in https://r.android.com/q/topic:max-target-s
Add @UnsupportedAppUsage maxTargetSdk=S in classes in framework-permission (for r/q)
and framework-permission-s (nominally for S+). I had to incresed the min_sdk_version
in the latter to 31 (S) as it was still set at 30 (R).
Change-Id: Ifd18e0bcf578d6d77b0ba7eee5770854d525a2b3
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h
index aef4a73..7415b93 100644
--- a/libartbase/base/hiddenapi_flags.h
+++ b/libartbase/base/hiddenapi_flags.h
@@ -254,22 +254,6 @@
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_; }
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index a4072ed..aee3f9a 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -922,7 +922,8 @@
} 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=")));
+ std::string value = std::string(option.substr(strlen("--max-hiddenapi-level=")));
+ max_hiddenapi_level_ = ApiList::FromName(value);
} else {
Usage("Unknown argument '%s'", raw_option);
}
@@ -985,6 +986,7 @@
std::map<std::string, ApiList> api_list = OpenApiFile(api_flags_path_);
// Iterate over input dex files and insert HiddenapiClassData sections.
+ bool max_hiddenapi_level_error = false;
for (size_t i = 0; i < boot_dex_paths_.size(); ++i) {
const std::string& input_path = boot_dex_paths_[i];
const std::string& output_path = output_dex_paths_[i];
@@ -1001,11 +1003,22 @@
builder.BeginClassDef(boot_class.GetClassDefIndex());
if (boot_class.GetData() != nullptr) {
auto fn_shared = [&](const DexMember& boot_member) {
- auto it = api_list.find(boot_member.GetApiEntry());
+ auto signature = boot_member.GetApiEntry();
+ auto it = api_list.find(signature);
bool api_list_found = (it != api_list.end());
CHECK(!force_assign_all_ || api_list_found)
- << "Could not find hiddenapi flags for dex entry: " << boot_member.GetApiEntry();
- builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
+ << "Could not find hiddenapi flags for dex entry: " << signature;
+ if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
+ ApiList without_domain(it->second.GetIntValue());
+ LOG(ERROR) << "Hidden api flag " << without_domain
+ << " for member " << signature
+ << " in " << input_path
+ << " exceeds maximum allowable flag "
+ << max_hiddenapi_level_;
+ max_hiddenapi_level_error = true;
+ } else {
+ builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
+ }
};
auto fn_field = [&](const ClassAccessor::Field& boot_field) {
fn_shared(DexMember(boot_class, boot_field));
@@ -1022,6 +1035,18 @@
dex_editor.Encode();
dex_editor.WriteTo(output_path);
}
+
+ if (max_hiddenapi_level_error) {
+ LOG(ERROR)
+ << "Some hidden API flags could not be encoded within the dex file as"
+ << " they exceed the maximum allowable level of " << max_hiddenapi_level_
+ << " which is determined by the min_sdk_version of the source Java library.\n"
+ << "The affected DEX members are reported in previous error messages.\n"
+ << "The unsupported flags are being generated from the maxTargetSdk property"
+ << " of the member's @UnsupportedAppUsage annotation.\n"
+ << "See b/172453495 and/or contact art-team@ or compat-team@ for more info.\n";
+ exit(EXIT_FAILURE);
+ }
}
std::map<std::string, ApiList> OpenApiFile(const std::string& path) {
@@ -1041,22 +1066,13 @@
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;
+ std::vector<std::string>::iterator apiListBegin = values.begin() + 1;
+ std::vector<std::string>::iterator apiListEnd = values.end();
bool success = ApiList::FromNames(apiListBegin, apiListEnd, &membership);
CHECK(success) << path << ":" << line_number
<< ": Some flags were not recognized: " << line << kErrorHelp;
@@ -1189,8 +1205,11 @@
// 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_;
+ // Maximum allowable hidden API level that can be encoded into the dex file.
+ //
+ // By default this returns a GetIntValue() that is guaranteed to be bigger than
+ // any valid value returned by GetIntValue().
+ ApiList max_hiddenapi_level_;
// Whether the input is only a fragment of the whole bootclasspath and may
// not include a complete set of classes. That requires the tool to ignore missing
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 2593edd..3a0e625 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -729,8 +729,7 @@
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));
+ ASSERT_EQ(dex_file.get(), nullptr);
}
TEST_F(HiddenApiTest, InstanceFieldMaxSdkEqualsMaxHiddenApiLevel) {