hiddenapi: Produce CSV for `list` command

The `hiddenapi` build tool is used for generating a list of all public
and private API. The output is currently two text files, each with
a list of API signatures belonging to the corresponding API set
(public/private). Refactor the code to produce a single CSV file
instead in anticipation of other categories of APIs, namely
@CorePlatformApi.

Also change class2greylist, which is a consumer of the public API text
file, to parse and filter the CSV file instead.

Bug: 119068555
Test: compiles, hiddenapi-flags.csv unchanged
Test: atest class2greylisttest
Change-Id: I4ac9d96c0d10a87795c6a779f0231269c99959a3
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h
index 8c1ffd5..b898828 100644
--- a/libartbase/base/hiddenapi_flags.h
+++ b/libartbase/base/hiddenapi_flags.h
@@ -78,6 +78,8 @@
   Value value_;
 
  public:
+  ApiList() : ApiList(Value::kInvalid) {}
+
   static ApiList Whitelist() { return ApiList(Value::kWhitelist); }
   static ApiList Greylist() { return ApiList(Value::kGreylist); }
   static ApiList Blacklist() { return ApiList(Value::kBlacklist); }
diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
index 1da848b..9c4e57e 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
@@ -17,6 +17,7 @@
 package com.android.class2greylist;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
@@ -34,10 +35,12 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Build time tool for extracting a list of members from jar files that have the @UsedByApps
@@ -67,7 +70,6 @@
     }
 
     private final Status mStatus;
-    private final String mPublicApiListFile;
     private final String mCsvFlagsFile;
     private final String mCsvMetadataFile;
     private final String[] mJarFiles;
@@ -77,10 +79,11 @@
     public static void main(String[] args) {
         Options options = new Options();
         options.addOption(OptionBuilder
-                .withLongOpt("public-api-list")
+                .withLongOpt("stub-api-flags")
                 .hasArgs(1)
-                .withDescription("Public API list file. Used to de-dupe bridge methods.")
-                .create("p"));
+                .withDescription("CSV file with API flags generated from public API stubs. " +
+                        "Used to de-dupe bridge methods.")
+                .create("s"));
         options.addOption(OptionBuilder
                 .withLongOpt("write-flags-csv")
                 .hasArgs(1)
@@ -139,7 +142,7 @@
             try {
                 Class2Greylist c2gl = new Class2Greylist(
                         status,
-                        cmd.getOptionValue('p', null),
+                        cmd.getOptionValue('s', null),
                         cmd.getOptionValue('w', null),
                         cmd.getOptionValue('c', null),
                         jarFiles);
@@ -158,11 +161,10 @@
     }
 
     @VisibleForTesting
-    Class2Greylist(Status status, String publicApiListFile, String csvFlagsFile,
+    Class2Greylist(Status status, String stubApiFlagsFile, String csvFlagsFile,
             String csvMetadataFile, String[] jarFiles)
             throws IOException {
         mStatus = status;
-        mPublicApiListFile = publicApiListFile;
         mCsvFlagsFile = csvFlagsFile;
         mCsvMetadataFile = csvMetadataFile;
         mJarFiles = jarFiles;
@@ -172,9 +174,13 @@
             mOutput = new HiddenapiFlagsWriter(mCsvFlagsFile);
         }
 
-        if (mPublicApiListFile != null) {
-            mPublicApis = Sets.newHashSet(
-                    Files.readLines(new File(mPublicApiListFile), Charset.forName("UTF-8")));
+        if (stubApiFlagsFile != null) {
+            mPublicApis =
+                    Files.readLines(new File(stubApiFlagsFile), Charset.forName("UTF-8")).stream()
+                        .map(s -> Splitter.on(",").splitToList(s))
+                        .filter(s -> s.contains(FLAG_WHITELIST))
+                        .map(s -> s.get(0))
+                        .collect(Collectors.toSet());
         } else {
             mPublicApis = Collections.emptySet();
         }
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index fdb0d8e..49e5282 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -33,6 +33,7 @@
 #include "dex/dex_file-inl.h"
 
 namespace art {
+namespace hiddenapi {
 
 const char kErrorHelp[] = "\nSee go/hiddenapi-error for help.";
 
@@ -83,12 +84,11 @@
   UsageError("");
   UsageError("  Command \"list\": dump lists of public and private API");
   UsageError("    --boot-dex=<filename>: dex file which belongs to boot class path");
-  UsageError("    --stub-classpath=<filenames>: colon-separated list of dex/apk files");
-  UsageError("        which form API stubs of boot class path. Multiple classpaths can");
-  UsageError("        be specified");
+  UsageError("    --public-stub-classpath=<filenames>:");
+  UsageError("        colon-separated list of dex/apk files which form API stubs of boot");
+  UsageError("        classpath. Multiple classpaths can be specified");
   UsageError("");
-  UsageError("    --out-public=<filename>: output file for a list of all public APIs");
-  UsageError("    --out-private=<filename>: output file for a list of all private APIs");
+  UsageError("    --out-api-flags=<filename>: output file for a CSV file with API flags");
   UsageError("");
 
   exit(EXIT_FAILURE);
@@ -619,7 +619,7 @@
   // Append flags at the end of the data struct. This should be called
   // between BeginClassDef and EndClassDef in the order of appearance of
   // fields/methods in the class data stream.
-  void WriteFlags(hiddenapi::ApiList flags) {
+  void WriteFlags(ApiList flags) {
     uint32_t uint_flags = flags.GetIntValue();
     EncodeUnsignedLeb128(&data_, uint_flags);
     class_def_has_non_zero_flags_ |= (uint_flags != 0u);
@@ -910,7 +910,7 @@
           } else if (option.starts_with("--output-dex=")) {
             output_dex_paths_.push_back(option.substr(strlen("--output-dex=")).ToString());
           } else if (option.starts_with("--api-flags=")) {
-            api_list_path_ = option.substr(strlen("--api-flags=")).ToString();
+            api_flags_path_ = option.substr(strlen("--api-flags=")).ToString();
           } else if (option == "--no-force-assign-all") {
             force_assign_all_ = false;
           } else {
@@ -923,13 +923,12 @@
           const StringPiece option(argv[i]);
           if (option.starts_with("--boot-dex=")) {
             boot_dex_paths_.push_back(option.substr(strlen("--boot-dex=")).ToString());
-          } else if (option.starts_with("--stub-classpath=")) {
-            stub_classpaths_.push_back(android::base::Split(
-                option.substr(strlen("--stub-classpath=")).ToString(), ":"));
-          } else if (option.starts_with("--out-public=")) {
-            out_public_path_ = option.substr(strlen("--out-public=")).ToString();
-          } else if (option.starts_with("--out-private=")) {
-            out_private_path_ = option.substr(strlen("--out-private=")).ToString();
+          } else if (option.starts_with("--public-stub-classpath=")) {
+            stub_classpaths_.push_back(std::make_pair(
+                option.substr(strlen("--public-stub-classpath=")).ToString(),
+                ApiList::Whitelist()));
+          } else if (option.starts_with("--out-api-flags=")) {
+            api_flags_path_ = option.substr(strlen("--out-api-flags=")).ToString();
           } else {
             Usage("Unknown argument '%s'", option.data());
           }
@@ -951,7 +950,7 @@
     }
 
     // Load dex signatures.
-    std::map<std::string, hiddenapi::ApiList> api_list = OpenApiFile(api_list_path_);
+    std::map<std::string, ApiList> api_list = OpenApiFile(api_flags_path_);
 
     // Iterate over input dex files and insert HiddenapiClassData sections.
     for (size_t i = 0; i < boot_dex_paths_.size(); ++i) {
@@ -975,7 +974,7 @@
               LOG(WARNING) << "Could not find hiddenapi flags for dex entry: "
                            << boot_member.GetApiEntry();
             }
-            builder.WriteFlags(api_list_found ? it->second : hiddenapi::ApiList::Whitelist());
+            builder.WriteFlags(api_list_found ? it->second : ApiList::Whitelist());
           };
           auto fn_field = [&](const ClassAccessor::Field& boot_field) {
             fn_shared(DexMember(boot_class, boot_field));
@@ -994,12 +993,12 @@
     }
   }
 
-  std::map<std::string, hiddenapi::ApiList> OpenApiFile(const std::string& path) {
+  std::map<std::string, ApiList> OpenApiFile(const std::string& path) {
     CHECK(!path.empty());
     std::ifstream api_file(path, std::ifstream::in);
     CHECK(!api_file.fail()) << "Unable to open file '" << path << "' " << strerror(errno);
 
-    std::map<std::string, hiddenapi::ApiList> api_flag_map;
+    std::map<std::string, ApiList> api_flag_map;
 
     int line_number = 1;
     for (std::string line; std::getline(api_file, line); line_number++) {
@@ -1035,15 +1034,13 @@
       Usage("No boot DEX files specified");
     } else if (stub_classpaths_.empty()) {
       Usage("No stub DEX files specified");
-    } else if (out_public_path_.empty()) {
-      Usage("No public API output path specified");
-    } else if (out_private_path_.empty()) {
-      Usage("No private API output path specified");
+    } else if (api_flags_path_.empty()) {
+      Usage("No output path specified");
     }
 
     // Complete list of boot class path members. The associated boolean states
     // whether it is public (true) or private (false).
-    std::map<std::string, bool> boot_members;
+    std::map<std::string, ApiList> boot_members;
 
     // Deduplicate errors before printing them.
     std::set<std::string> unresolved;
@@ -1053,31 +1050,34 @@
     Hierarchy boot_hierarchy(boot_classpath);
 
     // Mark all boot dex members private.
-    boot_classpath.ForEachDexMember([&boot_members](const DexMember& boot_member) {
-      boot_members[boot_member.GetApiEntry()] = false;
+    boot_classpath.ForEachDexMember([&](const DexMember& boot_member) {
+      boot_members[boot_member.GetApiEntry()] = ApiList::Invalid();
     });
 
     // Resolve each SDK dex member against the framework and mark it white.
-    for (const std::vector<std::string>& stub_classpath_dex : stub_classpaths_) {
-      ClassPath stub_classpath(stub_classpath_dex, /* open_writable= */ false);
+    for (const auto& cp_entry : stub_classpaths_) {
+      ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"),
+                               /* open_writable= */ false);
       Hierarchy stub_hierarchy(stub_classpath);
+      const ApiList stub_api_list = cp_entry.second;
+
       stub_classpath.ForEachDexMember(
-          [&stub_hierarchy, &boot_hierarchy, &boot_members, &unresolved](
-              const DexMember& stub_member) {
+          [&](const DexMember& stub_member) {
             if (!stub_hierarchy.IsMemberVisible(stub_member)) {
               // Typically fake constructors and inner-class `this` fields.
               return;
             }
             bool resolved = boot_hierarchy.ForEachResolvableMember(
                 stub_member,
-                [&boot_members](const DexMember& boot_member) {
+                [&](const DexMember& boot_member) {
                   std::string entry = boot_member.GetApiEntry();
                   auto it = boot_members.find(entry);
                   CHECK(it != boot_members.end());
-                  if (it->second) {
+                  if (it->second.IsValid()) {
+                    CHECK_EQ(it->second, stub_api_list);
                     return false;  // has been marked before
                   } else {
-                    it->second = true;
+                    it->second = stub_api_list;
                     return true;  // marked for the first time
                   }
                 });
@@ -1093,17 +1093,15 @@
     }
 
     // Write into public/private API files.
-    std::ofstream file_public(out_public_path_.c_str());
-    std::ofstream file_private(out_private_path_.c_str());
-    for (const std::pair<const std::string, bool>& entry : boot_members) {
-      if (entry.second) {
-        file_public << entry.first << std::endl;
+    std::ofstream file_flags(api_flags_path_.c_str());
+    for (const auto& entry : boot_members) {
+      if (entry.second.IsValid()) {
+        file_flags << entry.first << "," << entry.second << std::endl;
       } else {
-        file_private << entry.first << std::endl;
+        file_flags << entry.first << std::endl;
       }
     }
-    file_public.close();
-    file_private.close();
+    file_flags.close();
   }
 
   // Whether to check that all dex entries have been assigned flags.
@@ -1118,23 +1116,21 @@
 
   // Set of public API stub classpaths. Each classpath is formed by a list
   // of DEX/APK files in the order they appear on the classpath.
-  std::vector<std::vector<std::string>> stub_classpaths_;
+  std::vector<std::pair<std::string, ApiList>> stub_classpaths_;
 
-  // Paths to text files which contain the lists of API members.
-  std::string api_list_path_;
-
-  // Paths to text files to which we will output list of all API members.
-  std::string out_public_path_;
-  std::string out_private_path_;
+  // 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_;
 };
 
+}  // namespace hiddenapi
 }  // namespace art
 
 int main(int argc, char** argv) {
-  art::original_argc = argc;
-  art::original_argv = argv;
+  art::hiddenapi::original_argc = argc;
+  art::hiddenapi::original_argv = argv;
   android::base::InitLogging(argv);
   art::MemMap::Init();
-  art::HiddenApi().Run(argc, argv);
+  art::hiddenapi::HiddenApi().Run(argc, argv);
   return EXIT_SUCCESS;
 }