libredex/DexUtil.cpp (398 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "DexUtil.h"
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
#include <deque>
#include <string_view>
#include <unordered_set>
#include "Debug.h"
#include "DexClass.h"
#include "DexLoader.h"
#include "EditableCfgAdapter.h"
#include "ReachableClasses.h"
#include "Resolver.h"
#include "Trace.h"
#include "UnknownVirtuals.h"
const DexType* get_init_class_type_demand(const IRInstruction* insn) {
switch (insn->opcode()) {
case OPCODE_INVOKE_STATIC: {
// It's the resolved method that counts
auto method = resolve_method(insn->get_method(), opcode_to_search(insn));
return (method && !assumenosideeffects(method)) ? method->get_class()
: nullptr;
}
case OPCODE_SGET:
case OPCODE_SGET_WIDE:
case OPCODE_SGET_OBJECT:
case OPCODE_SGET_BOOLEAN:
case OPCODE_SGET_BYTE:
case OPCODE_SGET_CHAR:
case OPCODE_SGET_SHORT:
case OPCODE_SPUT:
case OPCODE_SPUT_WIDE:
case OPCODE_SPUT_OBJECT:
case OPCODE_SPUT_BOOLEAN:
case OPCODE_SPUT_BYTE:
case OPCODE_SPUT_CHAR:
case OPCODE_SPUT_SHORT: {
// It's the resolved field that counts
auto field = resolve_field(insn->get_field(), FieldSearch::Static);
return field ? field->get_class() : nullptr;
}
case IOPCODE_INIT_CLASS:
case OPCODE_NEW_INSTANCE: {
return insn->get_type();
}
default:
return nullptr;
}
}
DexAccessFlags merge_visibility(uint32_t vis1, uint32_t vis2) {
vis1 &= VISIBILITY_MASK;
vis2 &= VISIBILITY_MASK;
if ((vis1 & ACC_PUBLIC) || (vis2 & ACC_PUBLIC)) return ACC_PUBLIC;
if (vis1 == 0 || vis2 == 0) return static_cast<DexAccessFlags>(0);
if ((vis1 & ACC_PROTECTED) || (vis2 & ACC_PROTECTED)) return ACC_PROTECTED;
return ACC_PRIVATE;
}
void create_runtime_exception_block(const DexString* except_str,
std::vector<IRInstruction*>& block) {
// clang-format off
// new-instance v0, Ljava/lang/RuntimeException; // type@3852
// const-string v1, "Exception String e.g. Too many args" // string@7a6d
// invoke-direct {v0, v1}, Ljava/lang/RuntimeException;.<init>:(Ljava/lang/String;)V
// throw v0
// clang-format on
auto new_inst =
(new IRInstruction(OPCODE_NEW_INSTANCE))
->set_type(DexType::make_type("Ljava/lang/RuntimeException;"));
new_inst->set_dest(0);
IRInstruction* const_inst =
(new IRInstruction(OPCODE_CONST_STRING))->set_string(except_str);
const_inst->set_dest(1);
auto ret = DexType::make_type("V");
auto arg = DexType::make_type("Ljava/lang/String;");
auto args = DexTypeList::make_type_list({arg});
auto proto = DexProto::make_proto(ret, args);
auto meth =
DexMethod::make_method(DexType::make_type("Ljava/lang/RuntimeException;"),
DexString::make_string("<init>"), proto);
auto invk = new IRInstruction(OPCODE_INVOKE_DIRECT);
invk->set_method(meth);
invk->set_srcs_size(2);
invk->set_src(0, 0);
invk->set_src(1, 1);
IRInstruction* throwinst = new IRInstruction(OPCODE_THROW);
block.emplace_back(new_inst);
block.emplace_back(const_inst);
block.emplace_back(invk);
block.emplace_back(throwinst);
}
bool passes_args_through(IRInstruction* insn,
const IRCode& code,
int ignore /* = 0 */
) {
size_t src_idx{0};
size_t param_count{0};
for (const auto& mie : InstructionIterable(code.get_param_instructions())) {
auto load_param = mie.insn;
++param_count;
if (src_idx >= insn->srcs_size()) {
continue;
}
if (load_param->dest() != insn->src(src_idx++)) {
return false;
}
}
return insn->srcs_size() + ignore == param_count;
}
Scope build_class_scope(const DexStoresVector& stores) {
return build_class_scope(DexStoreClassesIterator(stores));
}
namespace {
template <typename PrefixIt>
bool starts_with_any_prefix(const DexString* str,
const PrefixIt& begin,
const PrefixIt& end) {
if (str == nullptr) {
return false;
}
auto it = begin;
while (it != end) {
if (boost::algorithm::starts_with(str->str(), *it)) {
return true;
}
it++;
}
return false;
}
} // namespace
Scope build_class_scope_for_packages(
const DexStoresVector& stores,
const std::unordered_set<std::string>& package_names) {
Scope v;
for (auto const& store : stores) {
for (auto& dex : store.get_dexen()) {
for (auto& clazz : dex) {
if (starts_with_any_prefix(clazz->get_deobfuscated_name_or_null(),
package_names.begin(),
package_names.end())) {
v.push_back(clazz);
}
}
}
}
return v;
}
void post_dexen_changes(const Scope& v, DexStoresVector& stores) {
DexStoreClassesIterator iter(stores);
post_dexen_changes(v, iter);
}
void load_root_dexen(DexStore& store,
const std::string& dexen_dir_str,
bool balloon,
bool throw_on_balloon_error,
bool verbose,
int support_dex_version) {
namespace fs = boost::filesystem;
fs::path dexen_dir_path(dexen_dir_str);
redex_assert(fs::is_directory(dexen_dir_path));
// Discover dex files
auto end = fs::directory_iterator();
std::vector<fs::path> dexen;
for (fs::directory_iterator it(dexen_dir_path); it != end; ++it) {
auto file = it->path();
if (fs::is_regular_file(file) &&
!file.extension().compare(std::string(".dex"))) {
dexen.emplace_back(file);
}
}
/*
* Comparator for dexen filename. 'classes.dex' should sort first,
* followed by [^\d]*[\d]+.dex ordered by N numerically.
*/
auto dex_comparator = [](const fs::path& a, const fs::path& b) {
boost::regex s_dex_regex("[^0-9]*([0-9]+)\\.dex");
auto as = a.filename().string();
auto bs = b.filename().string();
boost::smatch amatch;
boost::smatch bmatch;
bool amatched = boost::regex_match(as, amatch, s_dex_regex);
bool bmatched = boost::regex_match(bs, bmatch, s_dex_regex);
if (!amatched && bmatched) {
return true;
} else if (amatched && !bmatched) {
return false;
} else if (!amatched && !bmatched) {
// Compare strings, probably the same
return strcmp(as.c_str(), bs.c_str()) > 0;
} else {
// Compare captures as integers
auto anum = std::stoi(amatch[1]);
auto bnum = std::stoi(bmatch[1]);
return bnum > anum;
}
};
// Sort all discovered dex files
std::sort(dexen.begin(), dexen.end(), dex_comparator);
// Load all discovered dex files
for (const auto& dex : dexen) {
if (verbose) {
TRACE(MAIN, 1, "Loading %s", dex.string().c_str());
}
// N.B. throaway stats for now
DexClasses classes = load_classes_from_dex(
DexLocation::make_location("dex", dex.string()), balloon,
/* throw_on_balloon_error */ throw_on_balloon_error,
support_dex_version);
store.add_classes(std::move(classes));
}
}
void create_store(const std::string& store_name,
DexStoresVector& stores,
DexClasses classes) {
// First, remove the classes from other stores.
for (auto& store : stores) {
store.remove_classes(classes);
}
// Create a new store and add it to the list of stores.
DexStore store(store_name);
store.set_generated();
store.add_classes(std::move(classes));
stores.emplace_back(std::move(store));
}
void relocate_field(DexField* field, DexType* to_type) {
// change_visibility(field, to_type);
auto from_cls = type_class(field->get_class());
auto to_cls = type_class(to_type);
from_cls->remove_field(field);
DexFieldSpec spec;
spec.cls = to_type;
field->change(spec, true /* rename on collision */);
to_cls->add_field(field);
}
void relocate_method(DexMethod* method, DexType* to_type) {
change_visibility(method, to_type);
auto from_cls = type_class(method->get_class());
auto to_cls = type_class(to_type);
from_cls->remove_method(method);
DexMethodSpec spec;
spec.cls = to_type;
method->change(spec, true /* rename on collision */);
to_cls->add_method(method);
}
VisibilityChanges get_visibility_changes(const DexMethod* method,
DexType* scope) {
return get_visibility_changes(method->get_code(), scope, method);
}
void VisibilityChanges::insert(const VisibilityChanges& other) {
classes.insert(other.classes.begin(), other.classes.end());
fields.insert(other.fields.begin(), other.fields.end());
methods.insert(other.methods.begin(), other.methods.end());
}
void VisibilityChanges::apply() const {
for (auto cls : classes) {
set_public(cls);
}
for (auto field : fields) {
set_public(field);
}
for (auto method : methods) {
set_public(method);
}
}
bool VisibilityChanges::empty() const {
return classes.empty() && fields.empty() && methods.empty();
}
namespace {
struct VisibilityChangeGetter {
VisibilityChanges& changes;
DexType* scope;
const DexMethod* effective_caller_resolved_from;
void process_insn(IRInstruction* insn) {
if (insn->has_field()) {
auto cls = type_class(insn->get_field()->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
auto field = resolve_field(insn->get_field(),
opcode::is_an_sfield_op(insn->opcode())
? FieldSearch::Static
: FieldSearch::Instance);
if (field != nullptr && field->is_concrete()) {
if (!is_public(field)) {
changes.fields.insert(field);
}
cls = type_class(field->get_class());
if (!is_public(cls)) {
changes.classes.insert(cls);
}
}
} else if (insn->has_method()) {
auto cls = type_class(insn->get_method()->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
auto current_method =
resolve_method(insn->get_method(), opcode_to_search(insn),
effective_caller_resolved_from);
if (current_method != nullptr && current_method->is_concrete() &&
(scope == nullptr || current_method->get_class() != scope)) {
if (!is_public(current_method)) {
changes.methods.insert(current_method);
}
cls = type_class(current_method->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
} else if (insn->has_type()) {
auto type = insn->get_type();
auto cls = type_class(type);
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
}
void process_catch_types(const std::vector<DexType*>& types) {
for (auto type : types) {
auto cls = type_class(type);
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
}
};
} // namespace
VisibilityChanges get_visibility_changes(
const IRCode* code,
DexType* scope,
const DexMethod* effective_caller_resolved_from) {
always_assert(code != nullptr);
VisibilityChanges changes;
VisibilityChangeGetter getter{changes, scope, effective_caller_resolved_from};
editable_cfg_adapter::iterate(const_cast<IRCode*>(code),
[&getter](MethodItemEntry& mie) {
getter.process_insn(mie.insn);
return editable_cfg_adapter::LOOP_CONTINUE;
});
std::vector<DexType*> types;
code->gather_catch_types(types);
getter.process_catch_types(types);
return changes;
}
VisibilityChanges get_visibility_changes(
const cfg::ControlFlowGraph& cfg,
DexType* scope,
const DexMethod* effective_caller_resolved_from) {
VisibilityChanges changes;
VisibilityChangeGetter getter{changes, scope, effective_caller_resolved_from};
for (auto& mie :
cfg::InstructionIterable(const_cast<cfg::ControlFlowGraph&>(cfg))) {
getter.process_insn(mie.insn);
}
std::vector<DexType*> types;
cfg.gather_catch_types(types);
getter.process_catch_types(types);
return changes;
}
// Check that visibility / accessibility changes to the current method
// won't need to change a referenced method into a virtual or static one.
bool gather_invoked_methods_that_prevent_relocation(
const DexMethod* method,
std::unordered_set<DexMethodRef*>* methods_preventing_relocation) {
auto code = method->get_code();
always_assert(code);
bool can_relocate = true;
for (const auto& mie : InstructionIterable(code)) {
auto insn = mie.insn;
auto opcode = insn->opcode();
if (opcode::is_an_invoke(opcode)) {
auto meth =
resolve_method(insn->get_method(), opcode_to_search(insn), method);
if (!meth && opcode == OPCODE_INVOKE_VIRTUAL &&
unknown_virtuals::is_method_known_to_be_public(insn->get_method())) {
continue;
}
if (meth) {
always_assert(meth->is_def());
if (meth->is_external() && !is_public(meth)) {
meth = nullptr;
} else if (opcode == OPCODE_INVOKE_DIRECT && !method::is_init(meth)) {
meth = nullptr;
}
}
if (!meth) {
can_relocate = false;
if (!methods_preventing_relocation) {
break;
}
methods_preventing_relocation->emplace(insn->get_method());
}
}
}
return can_relocate;
}
bool relocate_method_if_no_changes(DexMethod* method, DexType* to_type) {
if (!gather_invoked_methods_that_prevent_relocation(method)) {
return false;
}
set_public(method);
relocate_method(method, to_type);
change_visibility(method);
return true;
}
bool is_valid_identifier(std::string_view s) {
if (s.empty()) {
// Identifiers must not be empty.
return false;
}
for (char c : s) {
switch (c) {
// Forbidden characters. This may not work for UTF encodings.
case '/':
case ';':
case '.':
case '[':
return false;
// Allow everything else.
default:
break;
}
}
return true;
}