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(" --input-dex=<filename>: dex file which belongs to boot class path");
UsageError(" --output-dex=<filename>: file to write encoded dex into");
UsageError(" input and output dex files are paired in order of appearance");
UsageError("");
UsageError(" --api-flags=<filename>:");
UsageError(" CSV file with signatures of methods/fields and their respective flags");
UsageError("");
UsageError(" --max-hiddenapi-level=<max-target-*>:");
UsageError(" the maximum hidden api level for APIs. If an API was originally restricted");
UsageError(" to a newer sdk, turn it into a regular unsupported API instead.");
UsageError(" instead. The full list of valid values is in hiddenapi_flags.h");
UsageError("");
UsageError(" --no-force-assign-all:");
UsageError(" Disable check that all dex entries have been assigned a flag");
UsageError("");
UsageError(" Command \"list\": dump lists of public and private API");
UsageError(" --dependency-stub-dex=<filename>: dex file containing API stubs provided");
UsageError(" by other parts of the bootclasspath. These are used to resolve");
UsageError(" dependencies in dex files specified in --boot-dex but do not appear in");
UsageError(" the output");
UsageError(" --boot-dex=<filename>: dex file which belongs to boot class path");
UsageError(" --public-stub-classpath=<filenames>:");
UsageError(" --system-stub-classpath=<filenames>:");
UsageError(" --test-stub-classpath=<filenames>:");
UsageError(" --core-platform-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-api-flags=<filename>: output file for a CSV file with API flags");
UsageError(" --fragment: the input is only a fragment of the whole bootclasspath and may");
UsageError(" not include a complete set of classes. That requires the tool to ignore");
UsageError(" missing classes and members. Specify --verbose to see the warnings.");
UsageError(" --verbose: output all warnings, even when --fragment is specified.");
UsageError("");
// Constructs a string with a unique signature of this class member.
std::string GetApiEntry() const {
std::stringstream ss;
ss << klass_.GetDescriptorView() << "->" << GetName() << (IsMethod() ? "" : ":")
<< GetSignature(); return ss.str();
}
inlinebooloperator==(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();
// Soundness check that they do match. if (equals) {
CHECK_EQ(IsVirtualMethod(), other.IsVirtualMethod());
}
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();
}
}
// Returns true if this class contains at least one member matching `other`. bool HasMatchingMember(const DexMember& other) { return ForEachMatchingMember(other, [](const DexMember&) { returntrue; });
}
// 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);
}
}
}
// First try to find a member matching `other` in this class. bool found = ForEachMatchingMember(other, fn);
// If not found, see if it is inherited from parents. Note that this will not // revisit parents already in `visited`. if (!found && allow_explore_up) { for (HierarchyClass* superclass : extends_) {
found |= superclass->ForEachResolvableMember_Impl(
other,
fn, /* allow_explore_up */ true, /* allow_explore_down */ false,
visited);
}
}
// If this is a virtual method, continue exploring into subclasses so as to visit // all overriding methods. Allow subclasses to explore their superclasses if this // is an interface. This is needed to find implementations of this interface's // methods inherited from superclasses (b/122551864). if (allow_explore_down && other.IsVirtualMethod()) { for (HierarchyClass* subclass : extended_by_) {
subclass->ForEachResolvableMember_Impl(
other,
fn, /* allow_explore_up */ GetOneDexClass().IsInterface(), /* allow_explore_down */ true,
visited);
}
}
return found;
}
template<typename Fn> bool ForEachMatchingMember(const DexMember& other, Fn fn) { bool found = false; auto compare_member = [&](const DexMember& member) { // TODO(dbrazdil): Check whether class of `other` can access `member`. if (member == other) {
found = true;
fn(member);
}
}; 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;
}
// 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:
Hierarchy(ClassPath& classpath, bool fragment, bool verbose) : classpath_(classpath) {
BuildClassHierarchy(fragment, verbose);
}
// 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().GetDescriptorView()); 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. returnfalse;
} elseif (member.GetDeclaringClass().IsPublic()) { // Member is public or protected, and class is public. It must be visible. returntrue;
} elseif (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. returnfalse;
} 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().GetDescriptorView());
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()); returnfalse; // do not explore deeper
} elseif (subclass->GetOneDexClass().IsPublic()) { // `subclass` inherits and exposes `member`.
visible = true; returnfalse; // do not explore deeper
} else { // `subclass` inherits `member` but does not expose it. returntrue; // explore deeper
}
}); return visible;
}
}
private:
HierarchyClass* FindClass(std::string_view descriptor) { auto it = classes_.find(descriptor); if (it == classes_.end()) { return nullptr;
} else { return &it->second;
}
}
void BuildClassHierarchy(bool fragment, bool verbose) { // 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.GetDescriptorView()].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;
}
auto add_extends = [&](const std::string_view& extends_desc) {
HierarchyClass* extends = FindClass(extends_desc); if (extends != nullptr) {
klass.AddExtends(*extends);
} elseif (!fragment || verbose) { auto severity = verbose ? ::android::base::WARNING : ::android::base::FATAL;
LOG(severity)
<< "Superclass/interface " << extends_desc
<< " of class " << dex_klass.GetDescriptorView() << " from dex file \""
<< dex_klass.GetDexFile().GetLocation() << "\" was not found. "
<< "Either it is missing or it appears later in the classpath spec.";
}
};
// Notify the builder that new flags for the next class def // will be written now. The builder records the current offset // into the header. void BeginClassDef(uint32_t idx) {
CHECK_EQ(next_class_def_idx_, idx);
CHECK_LT(idx, num_classdefs_);
GetOffsetArray()[idx] = GetCurrentDataSize();
class_def_has_non_zero_flags_ = false;
}
// Notify the builder that all flags for this class def have been // written. The builder updates the total size of the data struct // and may set offset for class def in header to zero if no data // has been written. void EndClassDef(uint32_t idx) {
CHECK_EQ(next_class_def_idx_, idx);
CHECK_LT(idx, num_classdefs_);
++next_class_def_idx_;
if (!class_def_has_non_zero_flags_) { // No need to store flags for this class. Remove the written flags // and set offset in header to zero.
data_.resize(GetOffsetArray()[idx]);
GetOffsetArray()[idx] = 0u;
}
if (idx == num_classdefs_ - 1) { if (dex_file_has_non_zero_flags_) { // This was the last class def and we have generated non-zero hiddenapi // flags. Update total size in the header.
*GetSizeField() = GetCurrentDataSize();
} else { // This was the last class def and we have not generated any non-zero // hiddenapi flags. Clear all the data.
data_.clear();
}
}
}
// 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(const ApiList& flags) {
uint32_t dex_flags = flags.GetDexFlags();
EncodeUnsignedLeb128(&data_, dex_flags);
class_def_has_non_zero_flags_ |= (dex_flags != 0u);
}
// Return backing data, assuming that all flags have been written. const std::vector<uint8_t>& GetData() const {
CHECK_EQ(next_class_def_idx_, num_classdefs_) << "Incomplete data"; return data_;
}
private: // Returns pointer to the size field in the header of this dex section.
uint32_t* GetSizeField() { // Assume malloc() aligns allocated memory to at least uint32_t.
CHECK(IsAligned<sizeof(uint32_t)>(data_.data())); returnreinterpret_cast<uint32_t*>(data_.data());
}
// Returns pointer to array of offsets (indexed by class def indices) in the // header of this dex section.
uint32_t* GetOffsetArray() { return &GetSizeField()[1]; }
uint32_t GetCurrentDataSize() const { return data_.size(); }
// Number of class defs in this dex file. const uint32_t num_classdefs_;
// Next expected class def index.
uint32_t next_class_def_idx_;
// Whether non-zero flags have been encountered for this class def. bool class_def_has_non_zero_flags_;
// Whether any non-zero flags have been encountered for this dex file. bool dex_file_has_non_zero_flags_;
// Vector containing the data of the built data structure.
std::vector<uint8_t> data_;
};
// Edits a dex file, inserting a new HiddenapiClassData section. class DexFileEditor final { public: // Add dex file to copy to output (possibly several files for multi-dex). void Add(const DexFile* dex, const std::vector<uint8_t>&& hiddenapi_data) {
inputs_.emplace_back(dex, std::move(hiddenapi_data));
}
// Writes the edited dex file into a file. void WriteTo(const std::string& path) {
CHECK_GT(inputs_.size(), 0u);
std::vector<uint8_t> output;
// Copy the old dex files into the backing data vector.
std::vector<size_t> header_offset; for (size_t i = 0; i < inputs_.size(); i++) { const DexFile* dex = inputs_[i].first;
header_offset.push_back(output.size());
std::copy(dex->Begin(), dex->End(), std::back_inserter(output));
// Clear the old map list (make it into padding). const dex::MapList* map = dex->GetMapList();
size_t map_off = dex->GetHeader().map_off_;
size_t map_size = sizeof(map->size_) + map->size_ * sizeof(map->list_[0]);
CHECK_LE(map_off, output.size()) << "Map list past the end of file";
CHECK_EQ(map_size, output.size() - map_off) << "Map list expected at the end of file";
std::fill_n(output.data() + map_off, map_size, 0);
}
// Append the hidden api data into the backing data vector.
std::vector<size_t> hiddenapi_offset; for (size_t i = 0; i < inputs_.size(); i++) { const std::vector<uint8_t>& hiddenapi_data = inputs_[i].second;
output.resize(RoundUp(output.size(), kHiddenapiClassDataAlignment)); // Align.
hiddenapi_offset.push_back(output.size());
std::copy(hiddenapi_data.begin(), hiddenapi_data.end(), std::back_inserter(output));
}
// Append modified map lists.
std::vector<uint32_t> map_list_offset; for (size_t i = 0; i < inputs_.size(); i++) {
output.resize(RoundUp(output.size(), kMapListAlignment)); // Align.
// Check the header entry.
CHECK(!items.empty());
CHECK_EQ(items[0].type_, DexFile::kDexTypeHeaderItem);
CHECK_EQ(items[0].offset_, header_offset[i]);
// Check and remove the old map list entry (it does not have to be last). auto is_map_list = [](auto it) { return it.type_ == DexFile::kDexTypeMapList; }; auto it = std::find_if(items.begin(), items.end(), is_map_list);
CHECK(it != items.end());
CHECK_EQ(it->offset_, dex->GetHeader().map_off_);
items.erase(it);
void EncodeAccessFlags() { if (boot_dex_paths_.empty()) {
Usage("No input DEX files specified");
} elseif (output_dex_paths_.size() != boot_dex_paths_.size()) {
Usage("Number of input DEX files does not match number of output DEX files");
}
// Iterate over input dex files and insert HiddenapiClassData sections. bool max_hiddenapi_level_error = false; for (size_t i = 0; i < boot_dex_paths_.size(); ++i) { const std::string& input_path = boot_dex_paths_[i]; const std::string& output_path = output_dex_paths_[i];
ClassPath boot_classpath({input_path}, /* ignore_empty= */ false);
DexFileEditor dex_editor; for (const DexFile* input_dex : boot_classpath.GetDexFiles()) {
HiddenapiClassDataBuilder builder(*input_dex);
boot_classpath.ForEachDexClass(input_dex, [&](const DexClass& boot_class) {
builder.BeginClassDef(boot_class.GetClassDefIndex()); if (boot_class.GetData() != nullptr) { auto fn_shared = [&](const DexMember& boot_member) { auto signature = boot_member.GetApiEntry(); auto it = api_list.find(signature); bool api_list_found = (it != api_list.end());
CHECK(!force_assign_all_ || api_list_found)
<< "Could not find hiddenapi flags for dex entry: " << signature; if (api_list_found && it->second.GetIntValue() > max_hiddenapi_level_.GetIntValue()) {
ApiList without_domain = ApiList::FromDexFlags(it->second.GetIntValue());
LOG(ERROR) << "Hidden api flag " << without_domain << " for member " << signature
<< " in " << input_path << " exceeds maximum allowable flag "
<< max_hiddenapi_level_;
max_hiddenapi_level_error = true;
} else {
builder.WriteFlags(api_list_found ? it->second : ApiList::Sdk());
}
}; auto fn_field = [&](const ClassAccessor::Field& boot_field) {
fn_shared(DexMember(boot_class, boot_field));
}; auto fn_method = [&](const ClassAccessor::Method& boot_method) {
fn_shared(DexMember(boot_class, boot_method));
};
boot_class.VisitFieldsAndMethods(fn_field, fn_field, fn_method, fn_method);
}
builder.EndClassDef(boot_class.GetClassDefIndex());
});
dex_editor.Add(input_dex, std::move(builder.GetData()));
}
dex_editor.WriteTo(output_path);
}
if (max_hiddenapi_level_error) {
LOG(ERROR)
<< "Some hidden API flags could not be encoded within the dex file as"
<< " they exceed the maximum allowable level of " << max_hiddenapi_level_
<< " which is determined by the min_sdk_version of the source Java library.\n"
<< "The affected DEX members are reported in previous error messages.\n"
<< "The unsupported flags are being generated from the maxTargetSdk property"
<< " of the member's @UnsupportedAppUsage annotation.\n"
<< "See b/172453495 and/or contact art-team@ or compat-team@ for more info.\n"; exit(EXIT_FAILURE);
}
}
size_t line_number = 1; bool errors = false; for (std::string line; std::getline(api_file, line); line_number++) { // Every line contains a comma separated list with the signature as the // first element and the api flags as the rest
std::vector<std::string> values = android::base::Split(line, ",");
CHECK_GT(values.size(), 1u) << path << ":" << line_number
<< ": No flags found: " << line << kErrorHelp;
// A special flag added to the set of flags in boot_members to indicate that // it should be excluded from the output. static constexpr std::string_view kExcludeFromOutput{"exclude-from-output"};
// Complete list of boot class path members. The associated boolean states // whether it is public (true) or private (false).
std::map<std::string, std::set<std::string_view>> boot_members;
// Deduplicate errors before printing them.
std::set<std::string> unresolved;
// Open all dex files.
ClassPath boot_classpath(boot_dex_paths_, /* ignore_empty= */ false);
Hierarchy boot_hierarchy(boot_classpath, fragment_, verbose_);
// Mark all boot dex members private.
boot_classpath.ForEachDexMember([&](const DexMember& boot_member) {
boot_members[boot_member.GetApiEntry()] = {};
});
// Open all dependency API stub dex files.
ClassPath dependency_classpath(dependency_stub_dex_paths_, /* ignore_empty= */ false);
// Mark all dependency API stub dex members as coming from the dependency.
dependency_classpath.ForEachDexMember([&](const DexMember& boot_member) {
boot_members[boot_member.GetApiEntry()] = {kExcludeFromOutput};
});
// Resolve each SDK dex member against the framework and mark it as SDK. for (constauto& cp_entry : stub_classpaths_) { // Ignore any empty stub jars as it just means that they provide no APIs // for the current kind, e.g. framework-sdkextensions does not provide // any public APIs.
ClassPath stub_classpath(android::base::Split(cp_entry.first, ":"), /*ignore_empty=*/true);
Hierarchy stub_hierarchy(stub_classpath, fragment_, verbose_); const ApiStubs::Kind stub_api = cp_entry.second;
stub_classpath.ForEachDexMember(
[&](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,
[&](const DexMember& boot_member) {
std::string entry = boot_member.GetApiEntry(); auto it = boot_members.find(entry);
CHECK(it != boot_members.end());
it->second.insert(ApiStubs::ToString(stub_api));
}); if (!resolved) {
unresolved.insert(stub_member.GetApiEntry());
}
});
}
// Write into public/private API files.
std::ofstream file_flags(api_flags_path_.c_str()); for (constauto& entry : boot_members) {
std::set<std::string_view> flags = entry.second; if (flags.empty()) { // There are no flags so it cannot be from the dependency stub API dex // files so just output the signature.
file_flags << entry.first << std::endl;
} elseif (flags.find(kExcludeFromOutput) == flags.end()) { // The entry has flags and is not from the dependency stub API dex so // output it.
file_flags << entry.first << ",";
file_flags << android::base::Join(entry.second, ",") << std::endl;
}
}
file_flags.close();
}
// Whether to check that all dex entries have been assigned flags. // Defaults to true. bool force_assign_all_;
// Paths to DEX files which should be processed.
std::vector<std::string> boot_dex_paths_;
// Paths to DEX files containing API stubs provided by other parts of the // boot class path which the DEX files in boot_dex_paths depend.
std::vector<std::string> dependency_stub_dex_paths_;
// Output paths where modified DEX files should be written.
std::vector<std::string> output_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::pair<std::string, ApiStubs::Kind>> stub_classpaths_;
// 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_;
// Maximum allowable hidden API level that can be encoded into the dex file. // // By default this returns a GetIntValue() that is guaranteed to be bigger than // any valid value returned by GetIntValue().
ApiList max_hiddenapi_level_ = ApiList::Invalid();
// Whether the input is only a fragment of the whole bootclasspath and may // not include a complete set of classes. That requires the tool to ignore missing // classes and members. bool fragment_ = false;
// Whether to output all warnings, even when `fragment_` is set. bool verbose_ = false;
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.