veridex: Add cmd line flag to filter app classes by name

Veridex can now skip analyzing classes which do not match a provided
filter. The filter is passed to veridex using '--app-class-filter='
command line flag as a comma-separated list of class descriptor
prefixes, e.g. 'Landroid/net/,Landroid/utils/' matches all classes in
the 'android.net' and 'android.utils' packages and their subpackages.

Test: compiles, manually test against APKs
Change-Id: I4db9c3625399ee313a1ba0aba7f3d9073570cc70
diff --git a/tools/veridex/class_filter.h b/tools/veridex/class_filter.h
new file mode 100644
index 0000000..aa74d53
--- /dev/null
+++ b/tools/veridex/class_filter.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef ART_TOOLS_VERIDEX_CLASS_FILTER_H_
+#define ART_TOOLS_VERIDEX_CLASS_FILTER_H_
+
+#include <android-base/strings.h>
+
+namespace art {
+
+class ClassFilter {
+ public:
+  explicit ClassFilter(const std::vector<std::string>& prefixes) : prefixes_(prefixes) {}
+
+  bool Matches(const char* class_descriptor) const {
+    if (prefixes_.empty()) {
+      return true;
+    }
+
+    for (const std::string& filter : prefixes_) {
+      if (android::base::StartsWith(class_descriptor, filter)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+ private:
+  const std::vector<std::string>& prefixes_;
+};
+
+}  // namespace art
+
+#endif  // ART_TOOLS_VERIDEX_CLASS_FILTER_H_
diff --git a/tools/veridex/hidden_api_finder.cc b/tools/veridex/hidden_api_finder.cc
index fe6d88a..3ab911e 100644
--- a/tools/veridex/hidden_api_finder.cc
+++ b/tools/veridex/hidden_api_finder.cc
@@ -51,7 +51,8 @@
   }
 }
 
-void HiddenApiFinder::CollectAccesses(VeridexResolver* resolver) {
+void HiddenApiFinder::CollectAccesses(VeridexResolver* resolver,
+                                      const ClassFilter& class_filter) {
   const DexFile& dex_file = resolver->GetDexFile();
   // Look at all types referenced in this dex file. Any of these
   // types can lead to being used through reflection.
@@ -64,110 +65,113 @@
   // Note: we collect strings constants only referenced in code items as the string table
   // contains other kind of strings (eg types).
   for (ClassAccessor accessor : dex_file.GetClasses()) {
-    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
-      for (const DexInstructionPcPair& inst : method.GetInstructions()) {
-        switch (inst->Opcode()) {
-          case Instruction::CONST_STRING: {
-            dex::StringIndex string_index(inst->VRegB_21c());
-            std::string name = std::string(dex_file.StringDataByIdx(string_index));
-            // Cheap filtering on the string literal. We know it cannot be a field/method/class
-            // if it contains a space.
-            if (name.find(' ') == std::string::npos) {
-              // Class names at the Java level are of the form x.y.z, but the list encodes
-              // them of the form Lx/y/z;. Inner classes have '$' for both Java level class
-              // names in strings, and hidden API lists.
-              std::string str = HiddenApi::ToInternalName(name);
-              // Note: we can query the lists directly, as HiddenApi added classes that own
-              // private methods and fields in them.
-              // We don't add class names to the `strings_` set as we know method/field names
-              // don't have '.' or '/'. All hidden API class names have a '/'.
-              if (hidden_api_.IsInAnyList(str)) {
-                classes_.insert(str);
-              } else if (hidden_api_.IsInAnyList(name)) {
-                // Could be something passed to JNI.
-                classes_.insert(name);
-              } else {
-                // We only keep track of the location for strings, as these will be the
-                // field/method names the user is interested in.
-                strings_.insert(name);
-                reflection_locations_[name].push_back(method.GetReference());
+    if (class_filter.Matches(accessor.GetDescriptor())) {
+      for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+        for (const DexInstructionPcPair& inst : method.GetInstructions()) {
+          switch (inst->Opcode()) {
+            case Instruction::CONST_STRING: {
+              dex::StringIndex string_index(inst->VRegB_21c());
+              std::string name = std::string(dex_file.StringDataByIdx(string_index));
+              // Cheap filtering on the string literal. We know it cannot be a field/method/class
+              // if it contains a space.
+              if (name.find(' ') == std::string::npos) {
+                // Class names at the Java level are of the form x.y.z, but the list encodes
+                // them of the form Lx/y/z;. Inner classes have '$' for both Java level class
+                // names in strings, and hidden API lists.
+                std::string str = HiddenApi::ToInternalName(name);
+                // Note: we can query the lists directly, as HiddenApi added classes that own
+                // private methods and fields in them.
+                // We don't add class names to the `strings_` set as we know method/field names
+                // don't have '.' or '/'. All hidden API class names have a '/'.
+                if (hidden_api_.IsInAnyList(str)) {
+                  classes_.insert(str);
+                } else if (hidden_api_.IsInAnyList(name)) {
+                  // Could be something passed to JNI.
+                  classes_.insert(name);
+                } else {
+                  // We only keep track of the location for strings, as these will be the
+                  // field/method names the user is interested in.
+                  strings_.insert(name);
+                  reflection_locations_[name].push_back(method.GetReference());
+                }
               }
+              break;
             }
-            break;
-          }
-          case Instruction::INVOKE_DIRECT:
-          case Instruction::INVOKE_INTERFACE:
-          case Instruction::INVOKE_STATIC:
-          case Instruction::INVOKE_SUPER:
-          case Instruction::INVOKE_VIRTUAL: {
-            CheckMethod(inst->VRegB_35c(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::INVOKE_DIRECT:
+            case Instruction::INVOKE_INTERFACE:
+            case Instruction::INVOKE_STATIC:
+            case Instruction::INVOKE_SUPER:
+            case Instruction::INVOKE_VIRTUAL: {
+              CheckMethod(inst->VRegB_35c(), resolver, method.GetReference());
+              break;
+            }
 
-          case Instruction::INVOKE_DIRECT_RANGE:
-          case Instruction::INVOKE_INTERFACE_RANGE:
-          case Instruction::INVOKE_STATIC_RANGE:
-          case Instruction::INVOKE_SUPER_RANGE:
-          case Instruction::INVOKE_VIRTUAL_RANGE: {
-            CheckMethod(inst->VRegB_3rc(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::INVOKE_DIRECT_RANGE:
+            case Instruction::INVOKE_INTERFACE_RANGE:
+            case Instruction::INVOKE_STATIC_RANGE:
+            case Instruction::INVOKE_SUPER_RANGE:
+            case Instruction::INVOKE_VIRTUAL_RANGE: {
+              CheckMethod(inst->VRegB_3rc(), resolver, method.GetReference());
+              break;
+            }
 
-          case Instruction::IGET:
-          case Instruction::IGET_WIDE:
-          case Instruction::IGET_OBJECT:
-          case Instruction::IGET_BOOLEAN:
-          case Instruction::IGET_BYTE:
-          case Instruction::IGET_CHAR:
-          case Instruction::IGET_SHORT: {
-            CheckField(inst->VRegC_22c(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::IGET:
+            case Instruction::IGET_WIDE:
+            case Instruction::IGET_OBJECT:
+            case Instruction::IGET_BOOLEAN:
+            case Instruction::IGET_BYTE:
+            case Instruction::IGET_CHAR:
+            case Instruction::IGET_SHORT: {
+              CheckField(inst->VRegC_22c(), resolver, method.GetReference());
+              break;
+            }
 
-          case Instruction::IPUT:
-          case Instruction::IPUT_WIDE:
-          case Instruction::IPUT_OBJECT:
-          case Instruction::IPUT_BOOLEAN:
-          case Instruction::IPUT_BYTE:
-          case Instruction::IPUT_CHAR:
-          case Instruction::IPUT_SHORT: {
-            CheckField(inst->VRegC_22c(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::IPUT:
+            case Instruction::IPUT_WIDE:
+            case Instruction::IPUT_OBJECT:
+            case Instruction::IPUT_BOOLEAN:
+            case Instruction::IPUT_BYTE:
+            case Instruction::IPUT_CHAR:
+            case Instruction::IPUT_SHORT: {
+              CheckField(inst->VRegC_22c(), resolver, method.GetReference());
+              break;
+            }
 
-          case Instruction::SGET:
-          case Instruction::SGET_WIDE:
-          case Instruction::SGET_OBJECT:
-          case Instruction::SGET_BOOLEAN:
-          case Instruction::SGET_BYTE:
-          case Instruction::SGET_CHAR:
-          case Instruction::SGET_SHORT: {
-            CheckField(inst->VRegB_21c(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::SGET:
+            case Instruction::SGET_WIDE:
+            case Instruction::SGET_OBJECT:
+            case Instruction::SGET_BOOLEAN:
+            case Instruction::SGET_BYTE:
+            case Instruction::SGET_CHAR:
+            case Instruction::SGET_SHORT: {
+              CheckField(inst->VRegB_21c(), resolver, method.GetReference());
+              break;
+            }
 
-          case Instruction::SPUT:
-          case Instruction::SPUT_WIDE:
-          case Instruction::SPUT_OBJECT:
-          case Instruction::SPUT_BOOLEAN:
-          case Instruction::SPUT_BYTE:
-          case Instruction::SPUT_CHAR:
-          case Instruction::SPUT_SHORT: {
-            CheckField(inst->VRegB_21c(), resolver, method.GetReference());
-            break;
-          }
+            case Instruction::SPUT:
+            case Instruction::SPUT_WIDE:
+            case Instruction::SPUT_OBJECT:
+            case Instruction::SPUT_BOOLEAN:
+            case Instruction::SPUT_BYTE:
+            case Instruction::SPUT_CHAR:
+            case Instruction::SPUT_SHORT: {
+              CheckField(inst->VRegB_21c(), resolver, method.GetReference());
+              break;
+            }
 
-          default:
-            break;
+            default:
+              break;
+          }
         }
       }
     }
   }
 }
 
-void HiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) {
+void HiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
+                          const ClassFilter& class_filter) {
   for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) {
-    CollectAccesses(resolver.get());
+    CollectAccesses(resolver.get(), class_filter);
   }
 }
 
diff --git a/tools/veridex/hidden_api_finder.h b/tools/veridex/hidden_api_finder.h
index 9e10c1a..f395e89 100644
--- a/tools/veridex/hidden_api_finder.h
+++ b/tools/veridex/hidden_api_finder.h
@@ -17,6 +17,7 @@
 #ifndef ART_TOOLS_VERIDEX_HIDDEN_API_FINDER_H_
 #define ART_TOOLS_VERIDEX_HIDDEN_API_FINDER_H_
 
+#include "class_filter.h"
 #include "dex/method_reference.h"
 
 #include <iostream>
@@ -39,12 +40,13 @@
 
   // Iterate over the dex files associated with the passed resolvers to report
   // hidden API uses.
-  void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers);
+  void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers,
+           const ClassFilter& app_class_filter);
 
   void Dump(std::ostream& os, HiddenApiStats* stats, bool dump_reflection);
 
  private:
-  void CollectAccesses(VeridexResolver* resolver);
+  void CollectAccesses(VeridexResolver* resolver, const ClassFilter& class_filter);
   void CheckMethod(uint32_t method_idx, VeridexResolver* resolver, MethodReference ref);
   void CheckField(uint32_t field_idx, VeridexResolver* resolver, MethodReference ref);
   void DumpReferences(std::ostream& os, const std::vector<MethodReference>& references);
diff --git a/tools/veridex/precise_hidden_api_finder.cc b/tools/veridex/precise_hidden_api_finder.cc
index be99ed2..7aa86dc 100644
--- a/tools/veridex/precise_hidden_api_finder.cc
+++ b/tools/veridex/precise_hidden_api_finder.cc
@@ -16,6 +16,7 @@
 
 #include "precise_hidden_api_finder.h"
 
+#include "class_filter.h"
 #include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_instruction-inl.h"
@@ -32,12 +33,15 @@
 
 void PreciseHiddenApiFinder::RunInternal(
     const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
+    const ClassFilter& class_filter,
     const std::function<void(VeridexResolver*, const ClassAccessor::Method&)>& action) {
   for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) {
     for (ClassAccessor accessor : resolver->GetDexFile().GetClasses()) {
-      for (const ClassAccessor::Method& method : accessor.GetMethods()) {
-        if (method.GetCodeItem() != nullptr) {
-          action(resolver.get(), method);
+      if (class_filter.Matches(accessor.GetDescriptor())) {
+        for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+          if (method.GetCodeItem() != nullptr) {
+            action(resolver.get(), method);
+          }
         }
       }
     }
@@ -55,9 +59,12 @@
   }
 }
 
-void PreciseHiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) {
+void PreciseHiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
+                                 const ClassFilter& class_filter) {
   // Collect reflection uses.
-  RunInternal(resolvers, [this] (VeridexResolver* resolver, const ClassAccessor::Method& method) {
+  RunInternal(resolvers,
+              class_filter,
+              [this] (VeridexResolver* resolver, const ClassAccessor::Method& method) {
     FlowAnalysisCollector collector(resolver, method);
     collector.Run();
     AddUsesAt(collector.GetUses(), method.GetReference());
@@ -73,6 +80,7 @@
     std::map<MethodReference, std::vector<ReflectAccessInfo>> current_uses
         = std::move(abstract_uses_);
     RunInternal(resolvers,
+                class_filter,
                 [this, current_uses] (VeridexResolver* resolver,
                                       const ClassAccessor::Method& method) {
       FlowAnalysisSubstitutor substitutor(resolver, method, current_uses);
diff --git a/tools/veridex/precise_hidden_api_finder.h b/tools/veridex/precise_hidden_api_finder.h
index 8c5126c..5254e84 100644
--- a/tools/veridex/precise_hidden_api_finder.h
+++ b/tools/veridex/precise_hidden_api_finder.h
@@ -17,6 +17,7 @@
 #ifndef ART_TOOLS_VERIDEX_PRECISE_HIDDEN_API_FINDER_H_
 #define ART_TOOLS_VERIDEX_PRECISE_HIDDEN_API_FINDER_H_
 
+#include "class_filter.h"
 #include "dex/method_reference.h"
 #include "flow_analysis.h"
 
@@ -40,7 +41,8 @@
 
   // Iterate over the dex files associated with the passed resolvers to report
   // hidden API uses.
-  void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers);
+  void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers,
+           const ClassFilter& app_class_filter);
 
   void Dump(std::ostream& os, HiddenApiStats* stats);
 
@@ -48,6 +50,7 @@
   // Run over all methods of all dex files, and call `action` on each.
   void RunInternal(
       const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
+      const ClassFilter& class_filter,
       const std::function<void(VeridexResolver*, const ClassAccessor::Method&)>& action);
 
   // Add uses found in method `ref`.
diff --git a/tools/veridex/veridex.cc b/tools/veridex/veridex.cc
index 3b6c7f9..bababce 100644
--- a/tools/veridex/veridex.cc
+++ b/tools/veridex/veridex.cc
@@ -17,6 +17,7 @@
 #include "veridex.h"
 
 #include <android-base/file.h>
+#include <android-base/strings.h>
 
 #include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
@@ -71,6 +72,7 @@
 static const char* kImprecise = "--imprecise";
 static const char* kTargetSdkVersion = "--target-sdk-version=";
 static const char* kOnlyReportSdkUses = "--only-report-sdk-uses";
+static const char* kAppClassFilter = "--app-class-filter=";
 
 struct VeridexOptions {
   const char* dex_file = nullptr;
@@ -79,6 +81,7 @@
   bool precise = true;
   int target_sdk_version = 28; /* P */
   bool only_report_sdk_uses = false;
+  std::vector<std::string> app_class_name_filter;
 };
 
 static const char* Substr(const char* str, int index) {
@@ -107,6 +110,11 @@
       options->target_sdk_version = atoi(Substr(argv[i], strlen(kTargetSdkVersion)));
     } else if (strcmp(argv[i], kOnlyReportSdkUses) == 0) {
       options->only_report_sdk_uses = true;
+    } else if (StartsWith(argv[i], kAppClassFilter)) {
+      options->app_class_name_filter = android::base::Split(
+          Substr(argv[i], strlen(kAppClassFilter)), ",");
+    } else {
+      LOG(WARNING) << "Unknown command line argument: " << argv[i];
     }
   }
 }
@@ -218,17 +226,19 @@
     std::vector<std::unique_ptr<VeridexResolver>> app_resolvers;
     Resolve(app_dex_files, resolver_map, type_map, &app_resolvers);
 
+    ClassFilter app_class_filter(options.app_class_name_filter);
+
     // Find and log uses of hidden APIs.
     HiddenApi hidden_api(options.flags_file, options.only_report_sdk_uses);
     HiddenApiStats stats;
 
     HiddenApiFinder api_finder(hidden_api);
-    api_finder.Run(app_resolvers);
+    api_finder.Run(app_resolvers, app_class_filter);
     api_finder.Dump(std::cout, &stats, !options.precise);
 
     if (options.precise) {
       PreciseHiddenApiFinder precise_api_finder(hidden_api);
-      precise_api_finder.Run(app_resolvers);
+      precise_api_finder.Run(app_resolvers, app_class_filter);
       precise_api_finder.Dump(std::cout, &stats);
     }