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);
}