static bool validate_sget(DexMethod* context, DexOpcodeField* opfield) { auto opcode = opfield->opcode(); switch (opcode) { case OPCODE_SGET_WIDE: unhandled_inline++; return false; case OPCODE_SGET: case OPCODE_SGET_BOOLEAN: case OPCODE_SGET_BYTE: case OPCODE_SGET_CHAR: case OPCODE_SGET_SHORT: return true; default: auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); always_assert_log( false, "Unexpected field type in inline_*sget %s for field %s value %s in " "method %s\n", SHOW(opfield), SHOW(field), value != nullptr ? value->show().c_str() : "('nullptr')", SHOW(context)); } return false; }
DexInstruction* DexInstruction::set_offset(int32_t offset) { auto format = opcode_format(opcode()); switch (format) { case FMT_f10t: always_assert_log((int32_t)(int8_t)(offset & 0xff) == offset, "offset %d too large for %s", offset, SHOW(this)); m_opcode = (m_opcode & 0xff) | ((offset & 0xff) << 8); return this; case FMT_f20t: case FMT_f21t: case FMT_f22t: always_assert_log((int32_t)(int16_t)(offset & 0xffff) == offset, "offset %d too large for %s", offset, SHOW(this)); m_arg[0] = offset; return this; case FMT_f30t: case FMT_f31t: m_arg[0] = offset; m_arg[1] = offset >> 16; return this; default: assert(false); } not_reached(); }
static void validate_dex_header(const dex_header* dh, size_t dexsize) { if (memcmp(dh->magic, DEX_HEADER_DEXMAGIC, sizeof(dh->magic))) { always_assert_log(false, "Bad dex magic %s\n", dh->magic); } always_assert_log( dh->file_size == dexsize, "Reported size in header (%z) does not match file size (%u)\n", dexsize, dh->file_size); }
ProguardMap::ProguardMap(const std::string& filename) { if (!filename.empty()) { std::ifstream fp(filename); always_assert_log(fp, "Can't open proguard map: %s\n", filename.c_str()); parse_proguard_map(fp); } }
uint16_t DexInstruction::dest() const { auto format = opcode_format(opcode()); switch (format) { case FMT_f12x: case FMT_f12x_2: case FMT_f11n: case FMT_f22s: case FMT_f22c_d: case FMT_f22cs: return (m_opcode >> 8) & 0xf; case FMT_f11x_d: case FMT_f22x: case FMT_f21s: case FMT_f21h: case FMT_f21c_d: case FMT_f23x_d: case FMT_f22b: case FMT_f31i: case FMT_f31c: case FMT_f51l: return (m_opcode >> 8) & 0xff; case FMT_f32x: return m_arg[0]; case FMT_f41c_d: case FMT_f52c_d: return m_arg[0]; default: // All other formats do not define a destination register. always_assert_log(false, "Unhandled opcode: %s", SHOW(this)); } not_reached(); }
/** * Read a map of {list_name : class_list} from json */ std::unordered_map<std::string, std::vector<std::string> > ConfigFiles::load_class_lists() { std::unordered_map<std::string, std::vector<std::string> > lists; std::string class_lists_filename; this->m_json.get("class_lists", "", class_lists_filename); if (class_lists_filename.empty()) { return lists; } std::ifstream input(class_lists_filename); Json::Reader reader; Json::Value root; bool parsing_succeeded = reader.parse(input, root); always_assert_log(parsing_succeeded, "Failed to parse class list json from file: %s\n%s", class_lists_filename.c_str(), reader.getFormattedErrorMessages().c_str()); for (Json::ValueIterator it = root.begin(); it != root.end(); ++it) { std::vector<std::string> class_list; Json::Value current_list = *it; for (Json::ValueIterator list_it = current_list.begin(); list_it != current_list.end(); ++list_it) { lists[it.key().asString()].push_back((*list_it).asString()); } } lists["secondary_dex_head.list"] = get_coldstart_classes(); return lists; }
size_t delete_methods( std::vector<DexClass*>& scope, std::unordered_set<DexMethod*>& removable, std::function<DexMethod*(DexMethod*, MethodSearch search)> resolver) { // if a removable candidate is invoked do not delete walk_opcodes(scope, [](DexMethod* meth) { return true; }, [&](DexMethod* meth, DexInstruction* insn) { if (is_invoke(insn->opcode())) { const auto mop = static_cast<DexOpcodeMethod*>(insn); auto callee = resolver(mop->get_method(), opcode_to_search(insn)); if (callee != nullptr) { removable.erase(callee); } } }); size_t deleted = 0; for (auto callee : removable) { if (!callee->is_concrete()) continue; if (do_not_strip(callee)) continue; auto cls = type_class(callee->get_class()); always_assert_log(cls != nullptr, "%s is concrete but does not have a DexClass\n", SHOW(callee)); if (callee->is_virtual()) { cls->get_vmethods().remove(callee); } else { cls->get_dmethods().remove(callee); } deleted++; TRACE(DELMET, 4, "removing %s\n", SHOW(callee)); } return deleted; }
std::vector<DexType*> find(const Scope& scope, const GetNewSpec& get_new_spec) { // Compute what the new prototypes will be after we convert a method. Check // the prototypes against existing methods and other prototypes created by // this method. std::vector<DexType*> result; for (const DexClass* cls : scope) { std::unordered_set<DexMethodSpec> new_specs; for (const DexMethod* m : cls->get_dmethods()) { if (is_init(m)) { std::vector<DexType*> unsafe_refs; const auto& new_spec = get_new_spec(m, &unsafe_refs); if (new_spec) { const auto& pair = new_specs.emplace(*new_spec); bool already_there = !pair.second; if (already_there || DexMethod::get_method(*new_spec)) { always_assert_log( !unsafe_refs.empty(), "unsafe_refs should be filled with the types that will be " "replaced on this <init> method's prototype"); result.insert(result.end(), unsafe_refs.begin(), unsafe_refs.end()); } } } } } return result; }
void IRList::insert_after(IRInstruction* position, const std::vector<IRInstruction*>& opcodes) { /* The nullptr case handling is strange-ish..., this will not work as expected * if a method has a branch target as it's first instruction. * * To handle this case sanely, we'd need to export a interface based on * MEI's probably. */ for (auto const& mei : m_list) { if (mei.type == MFLOW_OPCODE && (position == nullptr || mei.insn == position)) { auto insert_at = m_list.iterator_to(mei); if (position != nullptr) { insert_at++; if (position->has_move_result_pseudo()) { insert_at++; } } for (auto* opcode : opcodes) { MethodItemEntry* mentry = new MethodItemEntry(opcode); m_list.insert(insert_at, *mentry); } return; } } always_assert_log(false, "No match found"); }
DexOpcode invert_conditional_branch(DexOpcode op) { switch (op) { case DOPCODE_IF_EQ: return DOPCODE_IF_NE; case DOPCODE_IF_NE: return DOPCODE_IF_EQ; case DOPCODE_IF_LT: return DOPCODE_IF_GE; case DOPCODE_IF_GE: return DOPCODE_IF_LT; case DOPCODE_IF_GT: return DOPCODE_IF_LE; case DOPCODE_IF_LE: return DOPCODE_IF_GT; case DOPCODE_IF_EQZ: return DOPCODE_IF_NEZ; case DOPCODE_IF_NEZ: return DOPCODE_IF_EQZ; case DOPCODE_IF_LTZ: return DOPCODE_IF_GEZ; case DOPCODE_IF_GEZ: return DOPCODE_IF_LTZ; case DOPCODE_IF_GTZ: return DOPCODE_IF_LEZ; case DOPCODE_IF_LEZ: return DOPCODE_IF_GTZ; default: always_assert_log(false, "Invalid conditional opcode %s", SHOW(op)); } }
DexOpcode* DexOpcode::set_dest(uint16_t vreg) { auto format = opcode_format(opcode()); switch (format) { case FMT_f12x: case FMT_f12x_2: case FMT_f11n: case FMT_f22s: case FMT_f22c_d: case FMT_f22cs: assert((vreg & 0xf) == vreg); m_opcode = (m_opcode & 0xf0ff) | (vreg << 8); return this; case FMT_f11x_d: case FMT_f22x: case FMT_f21s: case FMT_f21h: case FMT_f21c_d: case FMT_f23x_d: case FMT_f22b: case FMT_f31i: case FMT_f31c: case FMT_f51l: assert((vreg & 0xff) == vreg); m_opcode = (m_opcode & 0x00ff) | (vreg << 8); return this; case FMT_f32x: m_arg[0] = vreg; return this; default: // All other formats do not define a destination register. always_assert_log(false, "Unhandled opcode: %s", SHOW(this)); } not_reached(); }
// We can't output regions with more than 2^16 code units. // But the IR has no such restrictions. This function splits up a large try // region into many small try regions that have the exact same catch // information. // // Also, try region boundaries must lie on instruction boundaries. void IRCode::split_and_insert_try_regions( uint32_t start, uint32_t end, const DexCatches& catches, std::vector<std::unique_ptr<DexTryItem>>* tries) { const auto& get_last_addr_before = [this](uint32_t requested_addr) { uint32_t valid_addr = 0; for (const auto& mie : *m_ir_list) { if (mie.type == MFLOW_DEX_OPCODE) { auto insn_size = mie.dex_insn->size(); if (valid_addr == requested_addr || valid_addr + insn_size > requested_addr) { return valid_addr; } valid_addr += insn_size; } } always_assert_log(false, "no valid address for %d", requested_addr); }; constexpr uint32_t max = std::numeric_limits<uint16_t>::max(); while (start < end) { auto size = (end - start <= max) ? end - start : get_last_addr_before(start + max) - start; auto tri = std::make_unique<DexTryItem>(start, size); tri->m_catches = catches; tries->push_back(std::move(tri)); start += size; } }
MethodCreator::MethodCreator(DexMethod* meth) : method(meth) , meth_code(meth->get_code()) { always_assert_log(meth->is_concrete(), "Method must be concrete or use the other ctor"); load_locals(meth); main_block = new MethodBlock(meth_code->main_block(), this); }
IRInstruction* primary_instruction_of_move_result_pseudo( IRList::iterator it) { --it; always_assert_log(it->type == MFLOW_OPCODE && it->insn->has_move_result_pseudo(), "%s does not have a move result pseudo", SHOW(*it)); return it->insn; }
DexClasses DexLoader::load_dex(const char* location, dex_stats_t* stats) { m_file.open(location, boost::iostreams::mapped_file::readonly); if (!m_file.is_open()) { fprintf(stderr, "error: cannot create memory-mapped file: %s\n", location); exit(EXIT_FAILURE); } auto dh = reinterpret_cast<const dex_header*>(m_file.const_data()); validate_dex_header(dh, m_file.size()); if (dh->class_defs_size == 0) { return DexClasses(0); } m_idx = new DexIdx(dh); auto off = (uint64_t)dh->class_defs_off; auto limit = off + dh->class_defs_size * sizeof(dex_class_def); always_assert_log(off < m_file.size(), "class_defs_off out of range"); always_assert_log(limit <= m_file.size(), "invalid class_defs_size"); m_class_defs = reinterpret_cast<const dex_class_def*>(m_file.const_data() + off); DexClasses classes(dh->class_defs_size); m_classes = &classes; auto lwork = new class_load_work[dh->class_defs_size]; auto wq = workqueue_mapreduce<class_load_work*, std::vector<std::exception_ptr>>( class_work, exc_reducer); for (uint32_t i = 0; i < dh->class_defs_size; i++) { lwork[i].dl = this; lwork[i].num = i; wq.add_item(&lwork[i]); } const auto exceptions = wq.run_all(); delete[] lwork; if (!exceptions.empty()) { // At least one of the workers raised an exception aggregate_exception ae(exceptions); throw ae; } gather_input_stats(stats, dh); return classes; }
const std::vector<DexMethod*>& get_vmethods(const DexType* type) { const DexClass* cls = type_class(type); if (cls == nullptr) { always_assert_log( type == get_object_type(), "Unknown type %s\n", SHOW(type)); create_object_class(); cls = type_class(type); } return cls->get_vmethods(); }
MethodCreator::MethodCreator(DexMethod* meth) : method(meth), meth_code(MethodTransform::get_new_method(method)), out_count(0), top_reg(0) { always_assert_log(meth->is_concrete(), "Method must be concrete or use the other ctor"); load_locals(meth); main_block = new MethodBlock(meth_code->main_block(), this); }
unsigned min_srcs_size(DexOpcode op) { switch (dex_opcode::format(op)) { case FMT_f00x: case FMT_f10x: case FMT_f11n: case FMT_f11x_d: case FMT_f10t: case FMT_f20t: case FMT_f21s: case FMT_f21h: case FMT_f21c_d: case FMT_f30t: case FMT_f31i: case FMT_f31c: case FMT_f3rc: case FMT_f51l: case FMT_f5rc: case FMT_f41c_d: case FMT_fopcode: case FMT_iopcode: return 0; case FMT_f12x: case FMT_f11x_s: case FMT_f22x: case FMT_f21t: case FMT_f21c_s: case FMT_f22b: case FMT_f22s: case FMT_f22c_d: case FMT_f32x: case FMT_f31t: case FMT_f41c_s: case FMT_f52c_d: return 1; case FMT_f12x_2: case FMT_f23x_d: case FMT_f22t: case FMT_f22c_s: case FMT_f52c_s: return 2; case FMT_f23x_s: return 3; case FMT_f35c: case FMT_f57c: return 0; case FMT_f20bc: case FMT_f22cs: case FMT_f35ms: case FMT_f35mi: case FMT_f3rms: case FMT_f3rmi: always_assert_log(false, "Unimplemented opcode `%s'", SHOW(op)); } not_reached(); }
unsigned DexInstruction::srcs_size() const { auto format = opcode_format(opcode()); switch (format) { case FMT_f00x: case FMT_f10x: case FMT_f11n: case FMT_f11x_d: case FMT_f10t: case FMT_f20t: case FMT_f21s: case FMT_f21h: case FMT_f21c_d: case FMT_f30t: case FMT_f31i: case FMT_f31c: case FMT_f51l: case FMT_f41c_d: case FMT_fopcode: return 0; case FMT_f12x: case FMT_f11x_s: case FMT_f22x: case FMT_f21t: case FMT_f21c_s: case FMT_f22b: case FMT_f22s: case FMT_f22c_d: case FMT_f32x: case FMT_f31t: case FMT_f3rc: case FMT_f41c_s: case FMT_f52c_d: case FMT_f5rc: return 1; case FMT_f12x_2: case FMT_f23x_d: case FMT_f22t: case FMT_f22c_s: case FMT_f52c_s: return 2; case FMT_f23x_s: return 3; case FMT_f35c: case FMT_f57c: return arg_word_count(); case FMT_f20bc: case FMT_f22cs: case FMT_f35ms: case FMT_f35mi: case FMT_f3rms: case FMT_f3rmi: always_assert_log(false, "Unimplemented opcode `%s'", SHOW(this)); } not_reached(); }
opcode::Branchingness MethodItemEntry::branchingness() const { switch (type) { case MFLOW_OPCODE: return opcode::branchingness(insn->opcode()); case MFLOW_DEX_OPCODE: always_assert_log(false, "Not expecting dex instructions here"); not_reached(); default: return opcode::BRANCH_NONE; } }
DexString* DexIdx::get_stringidx_fromdex(uint32_t stridx) { redex_assert(stridx < m_string_ids_size); uint32_t stroff = m_string_ids[stridx].offset; always_assert_log( stroff < ((dex_header*)m_dexbase)->file_size, "String data offset out of range"); const uint8_t* dstr = m_dexbase + stroff; /* Strip off uleb128 size encoding */ int utfsize = read_uleb128(&dstr); return DexString::make_string((const char*)dstr, utfsize); }
void IRList::remove_opcode(IRInstruction* insn) { for (auto& mei : m_list) { if (mei.type == MFLOW_OPCODE && mei.insn == insn) { auto it = m_list.iterator_to(mei); remove_opcode(it); return; } } always_assert_log(false, "No match found while removing '%s' from method", SHOW(insn)); }
SwitchMethodPartitioning::SwitchMethodPartitioning(IRCode* code, bool verify_default_case) : m_code(code) { m_code->build_cfg(/* editable */ true); auto& cfg = m_code->cfg(); // Note that a single-case switch can be compiled as either a switch opcode or // a series of if-* opcodes. We can use constant propagation to handle these // cases uniformly: to determine the case key, we use the inferred value of // the operand to the branching opcode in the successor blocks. cp::intraprocedural::FixpointIterator fixpoint( cfg, cp::ConstantPrimitiveAnalyzer()); fixpoint.run(ConstantEnvironment()); auto determining_reg = compute_prologue_blocks(&cfg, fixpoint, verify_default_case); // Find all the outgoing edges from the prologue blocks std::vector<cfg::Edge*> cases; for (const cfg::Block* prologue : m_prologue_blocks) { for (cfg::Edge* e : prologue->succs()) { if (std::find(m_prologue_blocks.begin(), m_prologue_blocks.end(), e->target()) == m_prologue_blocks.end()) { cases.push_back(e); } } } for (auto edge : cases) { auto case_block = edge->target(); auto env = fixpoint.get_entry_state_at(case_block); auto case_key = env.get<SignedConstantDomain>(*determining_reg); if (case_key.is_top() && verify_default_case) { auto last_insn_it = case_block->get_last_insn(); always_assert_log(last_insn_it != case_block->end() && last_insn_it->insn->opcode() == OPCODE_THROW, "Could not determine key for block that does not look " "like it throws an IllegalArgumentException: %d in %s", case_block->id(), SHOW(cfg)); } else if (!case_key.is_top()) { const auto& c = case_key.get_constant(); if (c != boost::none) { m_key_to_block[*c] = case_block; } else { // handle multiple case keys that map to a single block always_assert(edge->type() == cfg::EDGE_BRANCH); const auto& edge_case_key = edge->case_key(); always_assert(edge_case_key != boost::none); m_key_to_block[*edge_case_key] = case_block; } } } }
void inline_sget(DexMethod* method, DexOpcodeField* opfield) { if (!validate_sget(method, opfield)) return; auto opcode = OPCODE_CONST; auto dest = opfield->dest(); auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); /* FIXME for sget_wide case */ uint32_t v = value != nullptr ? (uint32_t)value->value() : 0; auto newopcode = (new DexInstruction(opcode))->set_dest(dest)->set_literal(v); replace_opcode(method, opfield, newopcode); }
void inline_cheap_sget(DexMethod* method, DexOpcodeField* opfield) { if (!validate_sget(method, opfield)) return; auto dest = opfield->dest(); auto field = resolve_field(opfield->field(), FieldSearch::Static); always_assert_log(field->is_concrete(), "Must be a concrete field"); auto value = field->get_static_value(); /* FIXME for sget_wide case */ uint32_t v = value != nullptr ? (uint32_t)value->value() : 0; auto opcode = [&] { if ((v & 0xffff) == v) { return OPCODE_CONST_16; } else if ((v & 0xffff0000) == v) { return OPCODE_CONST_HIGH16; } always_assert_log(false, "Bad inline_cheap_sget queued up, can't fit to" " CONST_16 or CONST_HIGH16, bailing\n"); }(); auto newopcode = (new DexInstruction(opcode, 0))->set_dest(dest)->set_literal(v); replace_opcode(method, opfield, newopcode); }
void MethodTransform::remove_opcode(DexInstruction* insn) { for (auto const& mei : *m_fmethod) { if (mei.type == MFLOW_OPCODE && mei.insn == insn) { m_fmethod->erase(m_fmethod->iterator_to(mei)); delete insn; return; } } always_assert_log(false, "No match found while removing '%s' from method %s", SHOW(insn), show_short(m_method).c_str()); }
void DexesStructure::add_class_no_checks(const MethodRefs& clazz_mrefs, const FieldRefs& clazz_frefs, const TypeRefs& clazz_trefs, DexClass* clazz) { always_assert_log(m_classes.count(clazz) == 0, "Can't emit the same class twice: %s!\n", SHOW(clazz)); auto laclazz = estimate_linear_alloc(clazz); m_current_dex.add_class_no_checks(clazz_mrefs, clazz_frefs, clazz_trefs, laclazz, clazz); m_classes.emplace(clazz); update_stats(clazz_mrefs, clazz_frefs, clazz); }
MethodCreator::MethodCreator(DexType* cls, DexString* name, DexProto* proto, DexAccessFlags access) : method(DexMethod::make_method(cls, name, proto)), meth_code(MethodTransform::get_new_method(method)), out_count(0), top_reg(0) { always_assert_log(!method->is_concrete(), "Method already defined"); method->set_access(access); load_locals(method); main_block = new MethodBlock(meth_code->main_block(), this); }
const VirtualScope& find_virtual_scope(const SignatureMap& sig_map, const DexMethod* meth) { const auto& protos = sig_map.find(meth->get_name()); always_assert(protos != sig_map.end()); const auto& scopes = protos->second.find(meth->get_proto()); always_assert(scopes != protos->second.end()); const auto meth_type = meth->get_class(); for (const auto& scope : scopes->second) { if (scope.type == get_object_type()) return scope; if (is_subclass(scope.type, meth_type)) return scope; } always_assert_log(false, "unreachable. Scope not found for %s\n", SHOW(meth)); }
void ProguardMap::parse_line(const std::string& line) { if (parse_class(line)) { return; } if (parse_field(line)) { return; } if (parse_method(line)) { return; } always_assert_log(false, "Bogus line encountered in proguard map: %s\n", line.c_str()); }