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