summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Brazdil <dbrazdil@google.com> 2018-06-28 11:56:41 +0100
committer David Brazdil <dbrazdil@google.com> 2018-07-03 17:21:53 +0100
commit0b6de0c0de6c760a6bdd45a37e92055f662ad4ff (patch)
treee6b1398d87f31392a0dc1ff99c1ba10efbbb782e
parent2258c2ef5f6cb25ff12a1dc6dfac1f868892c226 (diff)
hiddenapi: Add 'list' command to generate public/private API lists
In order to make decisions about non-SDK API restrictions, we need a complete list of public and private class members in the boot class path. This was previously done by Doclava but having to lower its Java view of the framework to dex level has proven cumbersome and error prone. This patch adds a new command to the `hiddenapi` build tool which builds the class hierarchy of boot class path and attempts to resolve each member of android.jar against it. Resolved members are dumped into a public API text file, the rest into a private API text file. Note that the resolution is not strictly the same as in ART and we may err on the side of caution. This should be revisited to comply with the spec. Bug: 79409988 Test: m out/target/common/obj/PACKAGING/hiddenapi-private-list.txt Change-Id: Ia82bcaad9347344aacf8dc6f7b093f769cd853ec
-rw-r--r--libdexfile/dex/dex_file.h3
-rw-r--r--tools/hiddenapi/hiddenapi.cc479
2 files changed, 429 insertions, 53 deletions
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index 714e42cb3c..2eca8c7de0 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -1243,6 +1243,9 @@ class ClassDataItemIterator {
bool IsAtMethod() const {
return pos_ >= EndOfInstanceFieldsPos();
}
+ bool IsAtVirtualMethod() const {
+ return pos_ >= EndOfDirectMethodsPos();
+ }
bool HasNextStaticField() const {
return pos_ < EndOfStaticFieldsPos();
}
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index 2e1ec5a541..c252a9b4ae 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -16,7 +16,8 @@
#include <fstream>
#include <iostream>
-#include <unordered_set>
+#include <map>
+#include <set>
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
@@ -72,24 +73,71 @@ NO_RETURN static void Usage(const char* fmt, ...) {
UsageError(" --blacklist=<filename>:");
UsageError(" text files with signatures of methods/fields to be annotated");
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-dex=<filename>: dex/apk file which belongs to SDK API stubs");
+ 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("");
exit(EXIT_FAILURE);
}
+template<typename E>
+static bool Contains(const std::vector<E>& vec, const E& elem) {
+ return std::find(vec.begin(), vec.end(), elem) != vec.end();
+}
+
class DexClass {
public:
DexClass(const DexFile& dex_file, uint32_t idx)
: dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {}
const DexFile& GetDexFile() const { return dex_file_; }
+ const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); }
const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; }
+ const dex::TypeIndex GetSuperclassIndex() const { return class_def_.superclass_idx_; }
- const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); }
+ bool HasSuperclass() const { return dex_file_.IsTypeIndexValid(GetSuperclassIndex()); }
+
+ std::string GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); }
+
+ std::string GetSuperclassDescriptor() const {
+ if (HasSuperclass()) {
+ return dex_file_.StringByTypeIdx(GetSuperclassIndex());
+ } else {
+ return "";
+ }
+ }
+
+ std::set<std::string> GetInterfaceDescriptors() const {
+ std::set<std::string> list;
+ const DexFile::TypeList* ifaces = dex_file_.GetInterfacesList(class_def_);
+ for (uint32_t i = 0; ifaces != nullptr && i < ifaces->Size(); ++i) {
+ list.insert(dex_file_.StringByTypeIdx(ifaces->GetTypeItem(i).type_idx_));
+ }
+ return list;
+ }
- const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); }
+ inline bool IsVisible() const { return HasAccessFlags(kAccPublic); }
+
+ inline bool Equals(const DexClass& other) const {
+ bool equals = GetDescriptor() == other.GetDescriptor();
+ if (equals) {
+ // TODO(dbrazdil): Check that methods/fields match as well once b/111116543 is fixed.
+ CHECK_EQ(GetAccessFlags(), other.GetAccessFlags());
+ CHECK_EQ(GetSuperclassDescriptor(), other.GetSuperclassDescriptor());
+ CHECK(GetInterfaceDescriptors() == other.GetInterfaceDescriptors());
+ }
+ return equals;
+ }
private:
+ uint32_t GetAccessFlags() const { return class_def_.access_flags_; }
+ bool HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
+
const DexFile& dex_file_;
const DexFile::ClassDef& class_def_;
};
@@ -98,10 +146,12 @@ class DexMember {
public:
DexMember(const DexClass& klass, const ClassDataItemIterator& it)
: klass_(klass), it_(it) {
- DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_,
+ DCHECK_EQ(IsMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_,
klass_.GetClassIndex());
}
+ inline const DexClass& GetDeclaringClass() const { return klass_; }
+
// Sets hidden bits in access flags and writes them back into the DEX in memory.
// Note that this will not update the cached data of ClassDataItemIterator
// until it iterates over this item again and therefore will fail a CHECK if
@@ -115,7 +165,7 @@ class DexMember {
// `ptr` initially points to the next ClassData item. We iterate backwards
// until we hit the terminating byte of the previous Leb128 value.
const uint8_t* ptr = it_.DataPointer();
- if (it_.IsAtMethod()) {
+ if (IsMethod()) {
ptr = ReverseSearchUnsignedLeb128(ptr);
DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), it_.GetMethodCodeItemOffset());
}
@@ -126,30 +176,57 @@ class DexMember {
UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags);
}
+ inline bool IsMethod() const { return it_.IsAtMethod(); }
+ inline bool IsVirtualMethod() const { return it_.IsAtVirtualMethod(); }
+
+ // Returns true if the member is public/protected and is in a public class.
+ inline bool IsVisible() const {
+ return GetDeclaringClass().IsVisible() &&
+ (HasAccessFlags(kAccPublic) || HasAccessFlags(kAccProtected));
+ }
+
// Constructs a string with a unique signature of this class member.
std::string GetApiEntry() const {
std::stringstream ss;
- ss << klass_.GetDescriptor() << "->";
- if (it_.IsAtMethod()) {
- const DexFile::MethodId& mid = GetMethodId();
- ss << klass_.GetDexFile().GetMethodName(mid)
- << klass_.GetDexFile().GetMethodSignature(mid).ToString();
- } else {
- const DexFile::FieldId& fid = GetFieldId();
- ss << klass_.GetDexFile().GetFieldName(fid) << ":"
- << klass_.GetDexFile().GetFieldTypeDescriptor(fid);
- }
+ ss << klass_.GetDescriptor() << "->" << GetName() << (IsMethod() ? "" : ":") << GetSignature();
return ss.str();
}
+ inline bool operator==(const DexMember& other) {
+ // These need to match if they should resolve to one another.
+ bool equals = IsMethod() == other.IsMethod() &&
+ GetName() == other.GetName() &&
+ GetSignature() == other.GetSignature();
+
+ // Sanity checks if they do match.
+ if (equals) {
+ CHECK_EQ(IsVirtualMethod(), other.IsVirtualMethod());
+ }
+
+ return equals;
+ }
+
private:
+ inline uint32_t GetAccessFlags() const { return it_.GetMemberAccessFlags(); }
+ inline uint32_t HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
+
+ inline std::string GetName() const {
+ return IsMethod() ? klass_.GetDexFile().GetMethodName(GetMethodId())
+ : klass_.GetDexFile().GetFieldName(GetFieldId());
+ }
+
+ inline std::string GetSignature() const {
+ return IsMethod() ? klass_.GetDexFile().GetMethodSignature(GetMethodId()).ToString()
+ : klass_.GetDexFile().GetFieldTypeDescriptor(GetFieldId());
+ }
+
inline const DexFile::MethodId& GetMethodId() const {
- DCHECK(it_.IsAtMethod());
+ DCHECK(IsMethod());
return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex());
}
inline const DexFile::FieldId& GetFieldId() const {
- DCHECK(!it_.IsAtMethod());
+ DCHECK(!IsMethod());
return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex());
}
@@ -159,26 +236,33 @@ class DexMember {
class ClassPath FINAL {
public:
- explicit ClassPath(const std::vector<std::string>& dex_paths) {
- OpenDexFiles(dex_paths);
+ ClassPath(const std::vector<std::string>& dex_paths, bool open_writable) {
+ OpenDexFiles(dex_paths, open_writable);
}
template<typename Fn>
- void ForEachDexMember(Fn fn) {
+ void ForEachDexClass(Fn fn) {
for (auto& dex_file : dex_files_) {
for (uint32_t class_idx = 0; class_idx < dex_file->NumClassDefs(); ++class_idx) {
DexClass klass(*dex_file, class_idx);
- const uint8_t* klass_data = klass.GetData();
- if (klass_data != nullptr) {
- for (ClassDataItemIterator it(*dex_file, klass_data); it.HasNext(); it.Next()) {
- DexMember member(klass, it);
- fn(member);
- }
- }
+ fn(klass);
}
}
}
+ template<typename Fn>
+ void ForEachDexMember(Fn fn) {
+ ForEachDexClass([&fn](DexClass& klass) {
+ const uint8_t* klass_data = klass.GetData();
+ if (klass_data != nullptr) {
+ for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) {
+ DexMember member(klass, it);
+ fn(member);
+ }
+ }
+ });
+ }
+
void UpdateDexChecksums() {
for (auto& dex_file : dex_files_) {
// Obtain a writeable pointer to the dex header.
@@ -189,37 +273,231 @@ class ClassPath FINAL {
}
private:
- void OpenDexFiles(const std::vector<std::string>& dex_paths) {
+ void OpenDexFiles(const std::vector<std::string>& dex_paths, bool open_writable) {
ArtDexFileLoader dex_loader;
std::string error_msg;
- for (const std::string& filename : dex_paths) {
- File fd(filename.c_str(), O_RDWR, /* check_usage */ false);
- CHECK_NE(fd.Fd(), -1) << "Unable to open file '" << filename << "': " << strerror(errno);
-
- // Memory-map the dex file with MAP_SHARED flag so that changes in memory
- // propagate to the underlying file. We run dex file verification as if
- // the dex file was not in boot claass path to check basic assumptions,
- // such as that at most one of public/private/protected flag is set.
- // We do those checks here and skip them when loading the processed file
- // into boot class path.
- std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(),
- /* location */ filename,
- /* verify */ true,
- /* verify_checksum */ true,
- /* mmap_shared */ true,
- &error_msg));
- CHECK(dex_file.get() != nullptr) << "Open failed for '" << filename << "' " << error_msg;
- CHECK(dex_file->IsStandardDexFile()) << "Expected a standard dex file '" << filename << "'";
- CHECK(dex_file->EnableWrite())
- << "Failed to enable write permission for '" << filename << "'";
- dex_files_.push_back(std::move(dex_file));
+
+ if (open_writable) {
+ for (const std::string& filename : dex_paths) {
+ File fd(filename.c_str(), O_RDWR, /* check_usage */ false);
+ CHECK_NE(fd.Fd(), -1) << "Unable to open file '" << filename << "': " << strerror(errno);
+
+ // Memory-map the dex file with MAP_SHARED flag so that changes in memory
+ // propagate to the underlying file. We run dex file verification as if
+ // the dex file was not in boot claass path to check basic assumptions,
+ // such as that at most one of public/private/protected flag is set.
+ // We do those checks here and skip them when loading the processed file
+ // into boot class path.
+ std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(),
+ /* location */ filename,
+ /* verify */ true,
+ /* verify_checksum */ true,
+ /* mmap_shared */ true,
+ &error_msg));
+ CHECK(dex_file.get() != nullptr) << "Open failed for '" << filename << "' " << error_msg;
+ CHECK(dex_file->IsStandardDexFile()) << "Expected a standard dex file '" << filename << "'";
+ CHECK(dex_file->EnableWrite())
+ << "Failed to enable write permission for '" << filename << "'";
+ dex_files_.push_back(std::move(dex_file));
+ }
+ } else {
+ for (const std::string& filename : dex_paths) {
+ bool success = dex_loader.Open(filename.c_str(),
+ /* location */ filename,
+ /* verify */ true,
+ /* verify_checksum */ true,
+ &error_msg,
+ &dex_files_);
+ CHECK(success) << "Open failed for '" << filename << "' " << error_msg;
+ }
}
}
- // Opened DEX files. Note that these are opened as `const` but may be written into.
+ // Opened dex files. Note that these are opened as `const` but may be written into.
std::vector<std::unique_ptr<const DexFile>> dex_files_;
};
+class HierarchyClass FINAL {
+ public:
+ HierarchyClass() {}
+
+ void AddDexClass(const DexClass& klass) {
+ CHECK(dex_classes_.empty() || klass.Equals(dex_classes_.front()));
+ dex_classes_.push_back(klass);
+ }
+
+ void AddExtends(HierarchyClass& parent) {
+ CHECK(!Contains(extends_, &parent));
+ CHECK(!Contains(parent.extended_by_, this));
+ extends_.push_back(&parent);
+ parent.extended_by_.push_back(this);
+ }
+
+ const DexClass& GetOneDexClass() const {
+ CHECK(!dex_classes_.empty());
+ return dex_classes_.front();
+ }
+
+ // See comment on Hierarchy::ForEachResolvableMember.
+ template<typename Fn>
+ bool ForEachResolvableMember(const DexMember& other, Fn fn) {
+ return ForEachResolvableMember_Impl(other, fn) != ResolutionResult::kNotFound;
+ }
+
+ private:
+ // Result of resolution which takes into account whether the member was found
+ // for the first time or not. This is just a performance optimization to prevent
+ // re-visiting previously visited members.
+ // Note that order matters. When accumulating results, we always pick the maximum.
+ enum class ResolutionResult {
+ kNotFound,
+ kFoundOld,
+ kFoundNew,
+ };
+
+ inline ResolutionResult Accumulate(ResolutionResult a, ResolutionResult b) {
+ return static_cast<ResolutionResult>(
+ std::max(static_cast<unsigned>(a), static_cast<unsigned>(b)));
+ }
+
+ template<typename Fn>
+ ResolutionResult ForEachResolvableMember_Impl(const DexMember& other, Fn fn) {
+ // First try to find a member matching `other` in this class.
+ ResolutionResult foundInClass = ForEachMatchingMember(other, fn);
+
+ switch (foundInClass) {
+ case ResolutionResult::kFoundOld:
+ // A matching member was found and previously explored. All subclasses
+ // must have been explored too.
+ break;
+
+ case ResolutionResult::kFoundNew:
+ // A matching member was found and this was the first time it was visited.
+ // If it is a virtual method, visit all methods overriding/implementing it too.
+ if (other.IsVirtualMethod()) {
+ for (HierarchyClass* subclass : extended_by_) {
+ subclass->ForEachOverridingMember(other, fn);
+ }
+ }
+ break;
+
+ case ResolutionResult::kNotFound:
+ // A matching member was not found in this class. Explore the superclasses
+ // and implemented interfaces.
+ for (HierarchyClass* superclass : extends_) {
+ foundInClass = Accumulate(
+ foundInClass, superclass->ForEachResolvableMember_Impl(other, fn));
+ }
+ break;
+ }
+
+ return foundInClass;
+ }
+
+ template<typename Fn>
+ ResolutionResult ForEachMatchingMember(const DexMember& other, Fn fn) {
+ ResolutionResult found = ResolutionResult::kNotFound;
+ for (const DexClass& dex_class : dex_classes_) {
+ const uint8_t* data = dex_class.GetData();
+ if (data != nullptr) {
+ for (ClassDataItemIterator it(dex_class.GetDexFile(), data); it.HasNext(); it.Next()) {
+ DexMember member(dex_class, it);
+ if (member == other) {
+ found = Accumulate(found, fn(member) ? ResolutionResult::kFoundNew
+ : ResolutionResult::kFoundOld);
+ }
+ }
+ }
+ }
+ return found;
+ }
+
+ template<typename Fn>
+ void ForEachOverridingMember(const DexMember& other, Fn fn) {
+ CHECK(other.IsVirtualMethod());
+ ResolutionResult found = ForEachMatchingMember(other, fn);
+ if (found == ResolutionResult::kFoundOld) {
+ // No need to explore further.
+ return;
+ } else {
+ for (HierarchyClass* subclass : extended_by_) {
+ subclass->ForEachOverridingMember(other, fn);
+ }
+ }
+ }
+
+ // DexClass entries of this class found across all the provided dex files.
+ std::vector<DexClass> dex_classes_;
+
+ // Classes which this class inherits, or interfaces which it implements.
+ std::vector<HierarchyClass*> extends_;
+
+ // Classes which inherit from this class.
+ std::vector<HierarchyClass*> extended_by_;
+};
+
+class Hierarchy FINAL {
+ public:
+ explicit Hierarchy(ClassPath& class_path) : class_path_(class_path) {
+ BuildClassHierarchy();
+ }
+
+ // Perform an operation for each member of the hierarchy which could potentially
+ // be the result of method/field resolution of `other`.
+ // The function `fn` should accept a DexMember reference and return true if
+ // the member was changed. This drives a performance optimization which only
+ // visits overriding members the first time the overridden member is visited.
+ // Returns true if at least one resolvable member was found.
+ template<typename Fn>
+ bool ForEachResolvableMember(const DexMember& other, Fn fn) {
+ HierarchyClass* klass = FindClass(other.GetDeclaringClass().GetDescriptor());
+ return (klass != nullptr) && klass->ForEachResolvableMember(other, fn);
+ }
+
+ private:
+ HierarchyClass* FindClass(const std::string& descriptor) {
+ auto it = classes_.find(descriptor);
+ if (it == classes_.end()) {
+ return nullptr;
+ } else {
+ return &it->second;
+ }
+ }
+
+ void BuildClassHierarchy() {
+ // Create one HierarchyClass entry in `classes_` per class descriptor
+ // and add all DexClass objects with the same descriptor to that entry.
+ class_path_.ForEachDexClass([this](DexClass& klass) {
+ classes_[klass.GetDescriptor()].AddDexClass(klass);
+ });
+
+ // Connect each HierarchyClass to its successors and predecessors.
+ for (auto& entry : classes_) {
+ HierarchyClass& klass = entry.second;
+ const DexClass& dex_klass = klass.GetOneDexClass();
+
+ if (!dex_klass.HasSuperclass()) {
+ CHECK(dex_klass.GetInterfaceDescriptors().empty())
+ << "java/lang/Object should not implement any interfaces";
+ continue;
+ }
+
+ HierarchyClass* superclass = FindClass(dex_klass.GetSuperclassDescriptor());
+ CHECK(superclass != nullptr);
+ klass.AddExtends(*superclass);
+
+ for (const std::string& iface_desc : dex_klass.GetInterfaceDescriptors()) {
+ HierarchyClass* iface = FindClass(iface_desc);
+ CHECK(iface != nullptr);
+ klass.AddExtends(*iface);
+ }
+ }
+ }
+
+ ClassPath& class_path_;
+ std::map<std::string, HierarchyClass> classes_;
+};
+
class HiddenApi FINAL {
public:
HiddenApi() {}
@@ -229,14 +507,16 @@ class HiddenApi FINAL {
case Command::kEncode:
EncodeAccessFlags();
break;
+ case Command::kList:
+ ListApi();
+ break;
}
}
private:
enum class Command {
- // Currently just one command. A "list" command will be added for generating
- // a full list of boot class members.
kEncode,
+ kList,
};
Command ParseArgs(int argc, char** argv) {
@@ -262,6 +542,22 @@ class HiddenApi FINAL {
}
}
return Command::kEncode;
+ } else if (command == "list") {
+ for (int i = 1; i < argc; ++i) {
+ 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-dex=")) {
+ stub_dex_paths_.push_back(option.substr(strlen("--stub-dex=")).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 {
+ Usage("Unknown argument '%s'", option.data());
+ }
+ }
+ return Command::kList;
} else {
Usage("Unknown command '%s'", command.data());
}
@@ -282,7 +578,7 @@ class HiddenApi FINAL {
OpenApiFile(blacklist_path_, api_list, HiddenApiAccessFlags::kBlacklist);
// Open all dex files.
- ClassPath boot_class_path(boot_dex_paths_);
+ ClassPath boot_class_path(boot_dex_paths_, /* open_writable */ true);
// Set access flags of all members.
boot_class_path.ForEachDexMember([&api_list](DexMember& boot_member) {
@@ -315,13 +611,90 @@ class HiddenApi FINAL {
api_file.close();
}
+ void ListApi() {
+ if (boot_dex_paths_.empty()) {
+ Usage("No boot DEX files specified");
+ } else if (stub_dex_paths_.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");
+ }
+
+ // 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;
+
+ // Deduplicate errors before printing them.
+ std::set<std::string> unresolved;
+
+ // Open all dex files.
+ ClassPath stub_class_path(stub_dex_paths_, /* open_writable */ false);
+ ClassPath boot_class_path(boot_dex_paths_, /* open_writable */ false);
+ Hierarchy boot_hierarchy(boot_class_path);
+
+ // Mark all boot dex members private.
+ boot_class_path.ForEachDexMember([&boot_members](DexMember& boot_member) {
+ boot_members[boot_member.GetApiEntry()] = false;
+ });
+
+ // Resolve each SDK dex member against the framework and mark it white.
+ stub_class_path.ForEachDexMember(
+ [&boot_hierarchy, &boot_members, &unresolved](DexMember& stub_member) {
+ if (!stub_member.IsVisible()) {
+ // Typically fake constructors and inner-class `this` fields.
+ return;
+ }
+ bool resolved = boot_hierarchy.ForEachResolvableMember(
+ stub_member,
+ [&boot_members](DexMember& boot_member) {
+ std::string entry = boot_member.GetApiEntry();
+ auto it = boot_members.find(entry);
+ CHECK(it != boot_members.end());
+ if (it->second) {
+ return false; // has been marked before
+ } else {
+ boot_members.insert(it, std::make_pair(entry, true));
+ return true; // marked for the first time
+ }
+ });
+ if (!resolved) {
+ unresolved.insert(stub_member.GetApiEntry());
+ }
+ });
+
+ // Print errors.
+ for (const std::string& str : unresolved) {
+ LOG(WARNING) << "unresolved: " << str;
+ }
+
+ // 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<std::string, bool> entry : boot_members) {
+ if (entry.second) {
+ file_public << entry.first << std::endl;
+ } else {
+ file_private << entry.first << std::endl;
+ }
+ }
+ file_public.close();
+ file_private.close();
+ }
+
// Paths to DEX files which should be processed.
std::vector<std::string> boot_dex_paths_;
+ std::vector<std::string> stub_dex_paths_;
// Paths to text files which contain the lists of API members.
std::string light_greylist_path_;
std::string dark_greylist_path_;
std::string blacklist_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_;
};
} // namespace art