blob: 6d9b6fbe4085db28ca9ab1a502520b1b71c32377 [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "base/mem_map.h"
#include "base/os.h"
#include "base/unix_file/fd_file.h"
#include "dex/art_dex_file_loader.h"
#include "dex/class_accessor-inl.h"
#include "dex/dex_file-inl.h"
#include "dex/hidden_api_access_flags.h"
namespace art {
static int original_argc;
static char** original_argv;
static std::string CommandLine() {
std::vector<std::string> command;
for (int i = 0; i < original_argc; ++i) {
command.push_back(original_argv[i]);
}
return android::base::Join(command, ' ');
}
static void UsageErrorV(const char* fmt, va_list ap) {
std::string error;
android::base::StringAppendV(&error, fmt, ap);
LOG(ERROR) << error;
}
static void UsageError(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
UsageErrorV(fmt, ap);
va_end(ap);
}
NO_RETURN static void Usage(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
UsageErrorV(fmt, ap);
va_end(ap);
UsageError("Command: %s", CommandLine().c_str());
UsageError("Usage: hiddenapi [command_name] [options]...");
UsageError("");
UsageError(" Command \"encode\": encode API list membership in boot dex files");
UsageError(" --dex=<filename>: dex file which belongs to boot class path,");
UsageError(" the file will be overwritten");
UsageError("");
UsageError(" --light-greylist=<filename>:");
UsageError(" --dark-greylist=<filename>:");
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-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("");
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 ClassAccessor {
public:
explicit DexClass(const ClassAccessor& accessor) : ClassAccessor(accessor) {}
const uint8_t* GetData() const { return dex_file_.GetClassData(GetClassDef()); }
const dex::TypeIndex GetSuperclassIndex() const { return GetClassDef().superclass_idx_; }
bool HasSuperclass() const { return dex_file_.IsTypeIndexValid(GetSuperclassIndex()); }
std::string GetSuperclassDescriptor() const {
return HasSuperclass() ? dex_file_.StringByTypeIdx(GetSuperclassIndex()) : "";
}
std::set<std::string> GetInterfaceDescriptors() const {
std::set<std::string> list;
const DexFile::TypeList* ifaces = dex_file_.GetInterfacesList(GetClassDef());
for (uint32_t i = 0; ifaces != nullptr && i < ifaces->Size(); ++i) {
list.insert(dex_file_.StringByTypeIdx(ifaces->GetTypeItem(i).type_idx_));
}
return list;
}
inline bool IsPublic() const { return HasAccessFlags(kAccPublic); }
inline bool Equals(const DexClass& other) const {
bool equals = strcmp(GetDescriptor(), other.GetDescriptor()) == 0;
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 GetClassDef().access_flags_; }
bool HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
};
class DexMember {
public:
DexMember(const DexClass& klass, const ClassAccessor::Field& item)
: klass_(klass), item_(item), is_method_(false) {
DCHECK_EQ(GetFieldId().class_idx_, klass.GetClassIdx());
}
DexMember(const DexClass& klass, const ClassAccessor::Method& item)
: klass_(klass), item_(item), is_method_(true) {
DCHECK_EQ(GetMethodId().class_idx_, klass.GetClassIdx());
}
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 the class accessor
// until it iterates over this item again and therefore will fail a CHECK if
// it is called multiple times on the same DexMember.
void SetHidden(HiddenApiAccessFlags::ApiList value) const {
const uint32_t old_flags = item_.GetRawAccessFlags();
const uint32_t new_flags = HiddenApiAccessFlags::EncodeForDex(old_flags, value);
CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags));
// Locate the LEB128-encoded access flags in class data.
// `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 = item_.GetDataPointer();
if (IsMethod()) {
ptr = ReverseSearchUnsignedLeb128(ptr);
DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), GetMethod().GetCodeItemOffset());
}
ptr = ReverseSearchUnsignedLeb128(ptr);
DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), old_flags);
// Overwrite the access flags.
UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags);
}
inline bool IsMethod() const { return is_method_; }
inline bool IsVirtualMethod() const { return IsMethod() && !GetMethod().IsStaticOrDirect(); }
inline bool IsConstructor() const { return IsMethod() && HasAccessFlags(kAccConstructor); }
inline bool IsPublicOrProtected() const {
return 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() << "->" << GetName() << (IsMethod() ? "" : ":")
<< GetSignature();
return ss.str();
}
inline bool operator==(const DexMember& other) const {
// 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 item_.GetAccessFlags(); }
inline uint32_t HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
inline std::string GetName() const {
return IsMethod() ? item_.GetDexFile().GetMethodName(GetMethodId())
: item_.GetDexFile().GetFieldName(GetFieldId());
}
inline std::string GetSignature() const {
return IsMethod() ? item_.GetDexFile().GetMethodSignature(GetMethodId()).ToString()
: item_.GetDexFile().GetFieldTypeDescriptor(GetFieldId());
}
inline const ClassAccessor::Method& GetMethod() const {
DCHECK(IsMethod());
return down_cast<const ClassAccessor::Method&>(item_);
}
inline const DexFile::MethodId& GetMethodId() const {
DCHECK(IsMethod());
return item_.GetDexFile().GetMethodId(item_.GetIndex());
}
inline const DexFile::FieldId& GetFieldId() const {
DCHECK(!IsMethod());
return item_.GetDexFile().GetFieldId(item_.GetIndex());
}
const DexClass& klass_;
const ClassAccessor::BaseItem& item_;
const bool is_method_;
};
class ClassPath final {
public:
ClassPath(const std::vector<std::string>& dex_paths, bool open_writable) {
OpenDexFiles(dex_paths, open_writable);
}
template<typename Fn>
void ForEachDexClass(Fn fn) {
for (auto& dex_file : dex_files_) {
for (ClassAccessor accessor : dex_file->GetClasses()) {
fn(DexClass(accessor));
}
}
}
template<typename Fn>
void ForEachDexMember(Fn fn) {
ForEachDexClass([&fn](const DexClass& klass) {
for (const ClassAccessor::Field& field : klass.GetFields()) {
fn(DexMember(klass, field));
}
for (const ClassAccessor::Method& method : klass.GetMethods()) {
fn(DexMember(klass, method));
}
});
}
void UpdateDexChecksums() {
for (auto& dex_file : dex_files_) {
// Obtain a writeable pointer to the dex header.
DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader());
// Recalculate checksum and overwrite the value in the header.
header->checksum_ = dex_file->CalculateChecksum();
}
}
private:
void OpenDexFiles(const std::vector<std::string>& dex_paths, bool open_writable) {
ArtDexFileLoader dex_loader;
std::string error_msg;
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.
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;
}
// Returns true if this class contains at least one member matching `other`.
bool HasMatchingMember(const DexMember& other) {
return ForEachMatchingMember(
other, [](const DexMember&) { return true; }) != ResolutionResult::kNotFound;
}
// Recursively iterates over all subclasses of this class and invokes `fn`
// on each one. If `fn` returns false for a particular subclass, exploring its
// subclasses is skipped.
template<typename Fn>
void ForEachSubClass(Fn fn) {
for (HierarchyClass* subclass : extended_by_) {
if (fn(subclass)) {
subclass->ForEachSubClass(fn);
}
}
}
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;
auto compare_member = [&](const DexMember& member) {
if (member == other) {
found = Accumulate(found, fn(member) ? ResolutionResult::kFoundNew
: ResolutionResult::kFoundOld);
}
};
for (const DexClass& dex_class : dex_classes_) {
for (const ClassAccessor::Field& field : dex_class.GetFields()) {
compare_member(DexMember(dex_class, field));
}
for (const ClassAccessor::Method& method : dex_class.GetMethods()) {
compare_member(DexMember(dex_class, method));
}
}
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& classpath) : classpath_(classpath) {
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);
}
// Returns true if `member`, which belongs to this classpath, is visible to
// code in child class loaders.
bool IsMemberVisible(const DexMember& member) {
if (!member.IsPublicOrProtected()) {
// Member is private or package-private. Cannot be visible.
return false;
} else if (member.GetDeclaringClass().IsPublic()) {
// Member is public or protected, and class is public. It must be visible.
return true;
} else if (member.IsConstructor()) {
// Member is public or protected constructor and class is not public.
// Must be hidden because it cannot be implicitly exposed by a subclass.
return false;
} else {
// Member is public or protected method, but class is not public. Check if
// it is exposed through a public subclass.
// Example code (`foo` exposed by ClassB):
// class ClassA { public void foo() { ... } }
// public class ClassB extends ClassA {}
HierarchyClass* klass = FindClass(member.GetDeclaringClass().GetDescriptor());
CHECK(klass != nullptr);
bool visible = false;
klass->ForEachSubClass([&visible, &member](HierarchyClass* subclass) {
if (subclass->HasMatchingMember(member)) {
// There is a member which matches `member` in `subclass`, either
// a virtual method overriding `member` or a field overshadowing
// `member`. In either case, `member` remains hidden.
CHECK(member.IsVirtualMethod() || !member.IsMethod());
return false; // do not explore deeper
} else if (subclass->GetOneDexClass().IsPublic()) {
// `subclass` inherits and exposes `member`.
visible = true;
return false; // do not explore deeper
} else {
// `subclass` inherits `member` but does not expose it.
return true; // explore deeper
}
});
return visible;
}
}
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.
classpath_.ForEachDexClass([this](const 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& classpath_;
std::map<std::string, HierarchyClass> classes_;
};
class HiddenApi final {
public:
HiddenApi() {}
void Run(int argc, char** argv) {
switch (ParseArgs(argc, argv)) {
case Command::kEncode:
EncodeAccessFlags();
break;
case Command::kList:
ListApi();
break;
}
}
private:
enum class Command {
kEncode,
kList,
};
Command ParseArgs(int argc, char** argv) {
// Skip over the binary's path.
argv++;
argc--;
if (argc > 0) {
const StringPiece command(argv[0]);
if (command == "encode") {
for (int i = 1; i < argc; ++i) {
const StringPiece option(argv[i]);
if (option.starts_with("--dex=")) {
boot_dex_paths_.push_back(option.substr(strlen("--dex=")).ToString());
} else if (option.starts_with("--light-greylist=")) {
light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString();
} else if (option.starts_with("--dark-greylist=")) {
dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString();
} else if (option.starts_with("--blacklist=")) {
blacklist_path_ = option.substr(strlen("--blacklist=")).ToString();
} else {
Usage("Unknown argument '%s'", option.data());
}
}
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-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 {
Usage("Unknown argument '%s'", option.data());
}
}
return Command::kList;
} else {
Usage("Unknown command '%s'", command.data());
}
} else {
Usage("No command specified");
}
}
void EncodeAccessFlags() {
if (boot_dex_paths_.empty()) {
Usage("No boot DEX files specified");
}
// Load dex signatures.
std::map<std::string, HiddenApiAccessFlags::ApiList> api_list;
OpenApiFile(light_greylist_path_, api_list, HiddenApiAccessFlags::kLightGreylist);
OpenApiFile(dark_greylist_path_, api_list, HiddenApiAccessFlags::kDarkGreylist);
OpenApiFile(blacklist_path_, api_list, HiddenApiAccessFlags::kBlacklist);
// Open all dex files.
ClassPath boot_classpath(boot_dex_paths_, /* open_writable */ true);
// Set access flags of all members.
boot_classpath.ForEachDexMember([&api_list](const DexMember& boot_member) {
auto it = api_list.find(boot_member.GetApiEntry());
boot_member.SetHidden(it == api_list.end() ? HiddenApiAccessFlags::kWhitelist : it->second);
});
boot_classpath.UpdateDexChecksums();
}
void OpenApiFile(const std::string& path,
std::map<std::string, HiddenApiAccessFlags::ApiList>& api_list,
HiddenApiAccessFlags::ApiList membership) {
if (path.empty()) {
return;
}
std::ifstream api_file(path, std::ifstream::in);
CHECK(!api_file.fail()) << "Unable to open file '" << path << "' " << strerror(errno);
for (std::string line; std::getline(api_file, line);) {
CHECK(api_list.find(line) == api_list.end())
<< "Duplicate entry: " << line << " (" << api_list[line] << " and " << membership << ")";
api_list.emplace(line, membership);
}
api_file.close();
}
void ListApi() {
if (boot_dex_paths_.empty()) {
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");
}
// 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 boot_classpath(boot_dex_paths_, /* open_writable */ false);
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;
});
// 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);
Hierarchy stub_hierarchy(stub_classpath);
stub_classpath.ForEachDexMember(
[&stub_hierarchy, &boot_hierarchy, &boot_members, &unresolved](
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) {
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 {
it->second = 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_;
// 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_;
// 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
int main(int argc, char** argv) {
android::base::InitLogging(argv);
art::MemMap::Init();
art::HiddenApi().Run(argc, argv);
return EXIT_SUCCESS;
}