libredex/IRList.cpp (836 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 "IRList.h" #include <iterator> #include <sstream> #include <vector> #include "DexClass.h" #include "DexDebugInstruction.h" #include "DexInstruction.h" #include "DexPosition.h" #include "DexUtil.h" #include "IRInstruction.h" #include "Show.h" bool TryEntry::operator==(const TryEntry& other) const { return type == other.type && *catch_start == *other.catch_start; } bool CatchEntry::operator==(const CatchEntry& other) const { if (catch_type != other.catch_type) return false; if (next == other.next) return true; if (next == nullptr || other.next == nullptr) return false; return *next == *other.next; } bool BranchTarget::operator==(const BranchTarget& other) const { if (type != other.type) return false; if (src == other.src) return true; if (src == nullptr || other.src == nullptr) return false; return *src == *other.src; } std::ostream& operator<<(std::ostream& os, const MethodItemType& type) { switch (type) { case MFLOW_TRY: os << "try"; return os; case MFLOW_CATCH: os << "catch"; return os; case MFLOW_OPCODE: os << "opcode"; return os; case MFLOW_DEX_OPCODE: os << "dex-opcode"; return os; case MFLOW_TARGET: os << "target"; return os; case MFLOW_DEBUG: os << "debug"; return os; case MFLOW_POSITION: os << "position"; return os; case MFLOW_SOURCE_BLOCK: os << "source-block"; return os; case MFLOW_FALLTHROUGH: os << "fallthrough"; return os; } os << "unknown type " << (uint32_t)type; return os; } MethodItemEntry::MethodItemEntry(std::unique_ptr<DexDebugInstruction> dbgop) : type(MFLOW_DEBUG), dbgop(std::move(dbgop)) {} MethodItemEntry::MethodItemEntry(std::unique_ptr<DexPosition> pos) : type(MFLOW_POSITION), pos(std::move(pos)) {} MethodItemEntry::MethodItemEntry(std::unique_ptr<SourceBlock> src_block) : type(MFLOW_SOURCE_BLOCK), src_block(std::move(src_block)) {} MethodItemEntry::MethodItemEntry(const MethodItemEntry& that) : type(that.type) { switch (type) { case MFLOW_TRY: tentry = that.tentry; break; case MFLOW_CATCH: centry = that.centry; break; case MFLOW_OPCODE: insn = that.insn; break; case MFLOW_DEX_OPCODE: dex_insn = that.dex_insn; break; case MFLOW_TARGET: target = that.target; break; case MFLOW_DEBUG: new (&dbgop) std::unique_ptr<DexDebugInstruction>(that.dbgop->clone()); break; case MFLOW_POSITION: new (&pos) std::unique_ptr<DexPosition>(new DexPosition(*that.pos)); break; case MFLOW_SOURCE_BLOCK: new (&src_block) std::unique_ptr<SourceBlock>(new SourceBlock(*that.src_block)); break; case MFLOW_FALLTHROUGH: break; } } MethodItemEntry::~MethodItemEntry() { switch (type) { case MFLOW_TRY: delete tentry; break; case MFLOW_CATCH: delete centry; break; case MFLOW_TARGET: delete target; break; case MFLOW_DEBUG: dbgop.~unique_ptr<DexDebugInstruction>(); break; case MFLOW_POSITION: pos.~unique_ptr<DexPosition>(); break; case MFLOW_SOURCE_BLOCK: src_block.~unique_ptr<SourceBlock>(); break; case MFLOW_OPCODE: case MFLOW_DEX_OPCODE: case MFLOW_FALLTHROUGH: /* nothing to delete */ break; } } void MethodItemEntry::replace_ir_with_dex(DexInstruction* dex_insn) { always_assert(type == MFLOW_OPCODE); delete this->insn; this->type = MFLOW_DEX_OPCODE; this->dex_insn = dex_insn; } void MethodItemEntry::gather_strings( std::vector<const DexString*>& lstring) const { switch (type) { case MFLOW_TRY: break; case MFLOW_CATCH: break; case MFLOW_OPCODE: insn->gather_strings(lstring); break; case MFLOW_DEX_OPCODE: dex_insn->gather_strings(lstring); break; case MFLOW_TARGET: break; case MFLOW_DEBUG: dbgop->gather_strings(lstring); break; case MFLOW_POSITION: // although DexPosition contains strings, these strings don't find their // way into the APK break; case MFLOW_SOURCE_BLOCK: case MFLOW_FALLTHROUGH: break; } } void MethodItemEntry::gather_methods( std::vector<DexMethodRef*>& lmethod) const { switch (type) { case MFLOW_TRY: case MFLOW_CATCH: case MFLOW_POSITION: case MFLOW_FALLTHROUGH: case MFLOW_TARGET: // SourceBlock does not keep the method reachable. case MFLOW_SOURCE_BLOCK: // DexDebugInstruction does not have method reference. case MFLOW_DEBUG: break; case MFLOW_OPCODE: insn->gather_methods(lmethod); break; case MFLOW_DEX_OPCODE: dex_insn->gather_methods(lmethod); break; } } void MethodItemEntry::gather_callsites( std::vector<DexCallSite*>& lcallsite) const { switch (type) { case MFLOW_TRY: case MFLOW_CATCH: case MFLOW_POSITION: case MFLOW_FALLTHROUGH: case MFLOW_TARGET: case MFLOW_SOURCE_BLOCK: break; case MFLOW_OPCODE: insn->gather_callsites(lcallsite); break; case MFLOW_DEX_OPCODE: dex_insn->gather_callsites(lcallsite); break; case MFLOW_DEBUG: dbgop->gather_callsites(lcallsite); break; } } void MethodItemEntry::gather_methodhandles( std::vector<DexMethodHandle*>& lmethodhandle) const { switch (type) { case MFLOW_TRY: break; case MFLOW_CATCH: break; case MFLOW_OPCODE: insn->gather_methodhandles(lmethodhandle); break; case MFLOW_DEX_OPCODE: dex_insn->gather_methodhandles(lmethodhandle); break; case MFLOW_TARGET: break; case MFLOW_DEBUG: dbgop->gather_methodhandles(lmethodhandle); break; case MFLOW_POSITION: break; case MFLOW_SOURCE_BLOCK: break; case MFLOW_FALLTHROUGH: break; } } void MethodItemEntry::gather_fields(std::vector<DexFieldRef*>& lfield) const { switch (type) { case MFLOW_TRY: break; case MFLOW_CATCH: break; case MFLOW_OPCODE: insn->gather_fields(lfield); break; case MFLOW_DEX_OPCODE: dex_insn->gather_fields(lfield); break; case MFLOW_TARGET: break; case MFLOW_DEBUG: dbgop->gather_fields(lfield); break; case MFLOW_POSITION: break; case MFLOW_SOURCE_BLOCK: break; case MFLOW_FALLTHROUGH: break; } } void MethodItemEntry::gather_types(std::vector<DexType*>& ltype) const { switch (type) { case MFLOW_TRY: break; case MFLOW_CATCH: if (centry->catch_type != nullptr) { ltype.push_back(centry->catch_type); } break; case MFLOW_OPCODE: insn->gather_types(ltype); break; case MFLOW_DEX_OPCODE: dex_insn->gather_types(ltype); break; case MFLOW_TARGET: break; case MFLOW_DEBUG: dbgop->gather_types(ltype); break; case MFLOW_POSITION: break; case MFLOW_SOURCE_BLOCK: break; case MFLOW_FALLTHROUGH: break; } } void MethodItemEntry::gather_init_classes(std::vector<DexType*>& ltype) const { if (type == MFLOW_OPCODE) { insn->gather_init_classes(ltype); } } opcode::Branchingness MethodItemEntry::branchingness() const { switch (type) { case MFLOW_OPCODE: return opcode::branchingness(insn->opcode()); case MFLOW_DEX_OPCODE: not_reached_log("Not expecting dex instructions here"); default: return opcode::BRANCH_NONE; } } MethodItemEntryCloner::MethodItemEntryCloner() { m_entry_map[nullptr] = nullptr; m_pos_map[nullptr] = nullptr; } MethodItemEntry* MethodItemEntryCloner::clone(const MethodItemEntry* mie) { const auto& pair = m_entry_map.emplace(mie, nullptr); auto& it = pair.first; bool was_already_there = !pair.second; if (was_already_there) { return it->second; } auto cloned_mie = new MethodItemEntry(*mie); it->second = cloned_mie; switch (cloned_mie->type) { case MFLOW_TRY: cloned_mie->tentry = new TryEntry(*cloned_mie->tentry); cloned_mie->tentry->catch_start = clone(cloned_mie->tentry->catch_start); return cloned_mie; case MFLOW_CATCH: cloned_mie->centry = new CatchEntry(*cloned_mie->centry); cloned_mie->centry->next = clone(cloned_mie->centry->next); return cloned_mie; case MFLOW_OPCODE: cloned_mie->insn = new IRInstruction(*cloned_mie->insn); if (cloned_mie->insn->has_data()) { cloned_mie->insn->set_data(cloned_mie->insn->get_data()->clone()); } return cloned_mie; case MFLOW_TARGET: cloned_mie->target = new BranchTarget(*cloned_mie->target); cloned_mie->target->src = clone(cloned_mie->target->src); return cloned_mie; case MFLOW_DEBUG: return cloned_mie; case MFLOW_POSITION: m_pos_map[mie->pos.get()] = cloned_mie->pos.get(); m_positions_to_fix.push_back(cloned_mie->pos.get()); return cloned_mie; case MFLOW_FALLTHROUGH: return cloned_mie; case MFLOW_SOURCE_BLOCK: return cloned_mie; case MFLOW_DEX_OPCODE: not_reached_log("DexInstructions not expected here"); } } void MethodItemEntryCloner::fix_parent_positions( const DexPosition* ignore_pos) { // When the DexPosition was copied, the parent pointer was shallowly copied for (DexPosition* pos : m_positions_to_fix) { if (pos->parent != ignore_pos) { pos->parent = m_pos_map.at(pos->parent); } } } bool MethodItemEntry::operator==(const MethodItemEntry& that) const { if (type != that.type) { return false; } switch (type) { case MFLOW_TRY: return *tentry == *that.tentry; case MFLOW_CATCH: return *centry == *that.centry; case MFLOW_OPCODE: return *insn == *that.insn; case MFLOW_DEX_OPCODE: return *dex_insn == *that.dex_insn; case MFLOW_TARGET: return *target == *that.target; case MFLOW_DEBUG: return *dbgop == *that.dbgop; case MFLOW_POSITION: return *pos == *that.pos; case MFLOW_SOURCE_BLOCK: return *src_block == *that.src_block; case MFLOW_FALLTHROUGH: return true; }; not_reached(); } // TODO: T62185151 - better way of applying this on CFGs void IRList::cleanup_debug(std::unordered_set<reg_t>& valid_regs) { auto it = m_list.begin(); while (it != m_list.end()) { auto next = std::next(it); if (it->type == MFLOW_DEBUG) { switch (it->dbgop->opcode()) { case DBG_SET_PROLOGUE_END: this->erase_and_dispose(it); break; case DBG_START_LOCAL: case DBG_START_LOCAL_EXTENDED: { auto reg = it->dbgop->uvalue(); valid_regs.insert(reg); break; } case DBG_END_LOCAL: case DBG_RESTART_LOCAL: { auto reg = it->dbgop->uvalue(); if (valid_regs.find(reg) == valid_regs.end()) { this->erase_and_dispose(it); } break; } default: break; } } it = next; } } void IRList::cleanup_debug() { std::unordered_set<reg_t> valid_regs; cleanup_debug(valid_regs); } void IRList::replace_opcode(IRInstruction* to_delete, const std::vector<IRInstruction*>& replacements) { auto it = m_list.begin(); for (; it != m_list.end(); it++) { if (it->type == MFLOW_OPCODE && it->insn == to_delete) { break; } } always_assert_log(it != m_list.end(), "No match found while replacing '%s'", SHOW(to_delete)); replace_opcode(it, replacements); } void IRList::replace_opcode(const IRList::iterator& it, const std::vector<IRInstruction*>& replacements) { always_assert(it->type == MFLOW_OPCODE); for (auto insn : replacements) { insert_before(it, insn); } remove_opcode(it); } void IRList::replace_opcode(IRInstruction* from, IRInstruction* to) { always_assert_log(!opcode::is_branch(to->opcode()), "You may want replace_branch instead"); replace_opcode(from, std::vector<IRInstruction*>{to}); } void IRList::replace_opcode_with_infinite_loop(IRInstruction* from) { IRInstruction* to = new IRInstruction(OPCODE_GOTO); auto miter = m_list.begin(); for (; miter != m_list.end(); miter++) { MethodItemEntry* mentry = &*miter; if (mentry->type == MFLOW_OPCODE && mentry->insn == from) { if (opcode::is_branch(from->opcode())) { remove_branch_targets(from); } mentry->insn = to; delete from; break; } } always_assert_log(miter != m_list.end(), "No match found while replacing '%s' with '%s'", SHOW(from), SHOW(to)); auto target = new BranchTarget(&*miter); m_list.insert(miter, *(new MethodItemEntry(target))); } void IRList::replace_branch(IRInstruction* from, IRInstruction* to) { always_assert(opcode::is_branch(from->opcode())); always_assert(opcode::is_branch(to->opcode())); for (auto& mentry : m_list) { if (mentry.type == MFLOW_OPCODE && mentry.insn == from) { mentry.insn = to; delete from; return; } } not_reached_log("No match found while replacing '%s' with '%s'", SHOW(from), SHOW(to)); } 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; } } not_reached_log("No match found"); } IRList::iterator IRList::insert_before(const IRList::iterator& position, MethodItemEntry& mie) { return m_list.insert(position, mie); } IRList::iterator IRList::insert_after(const IRList::iterator& position, MethodItemEntry& mie) { always_assert(position != m_list.end()); return m_list.insert(std::next(position), mie); } void IRList::remove_opcode(const IRList::iterator& it) { always_assert(it->type == MFLOW_OPCODE); auto insn = it->insn; always_assert(!opcode::is_a_move_result_pseudo(insn->opcode())); if (insn->has_move_result_pseudo()) { auto move_it = std::next(it); always_assert_log( move_it->type == MFLOW_OPCODE && opcode::is_a_move_result_pseudo(move_it->insn->opcode()), "No move-result-pseudo found for %s", SHOW(insn)); delete move_it->insn; move_it->type = MFLOW_FALLTHROUGH; move_it->insn = nullptr; } if (opcode::is_branch(insn->opcode())) { remove_branch_targets(insn); } it->type = MFLOW_FALLTHROUGH; it->insn = nullptr; delete insn; } 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; } } not_reached_log("No match found while removing '%s' from method", SHOW(insn)); } size_t IRList::sum_opcode_sizes() const { size_t size{0}; for (const auto& mie : m_list) { if (mie.type == MFLOW_OPCODE) { size += mie.insn->size(); } } return size; } size_t IRList::count_opcodes() const { size_t count{0}; for (const auto& mie : m_list) { if (mie.type == MFLOW_OPCODE && !opcode::is_an_internal(mie.insn->opcode())) { ++count; } } return count; } void IRList::sanity_check() const { std::unordered_set<const MethodItemEntry*> entries; for (const auto& mie : m_list) { entries.insert(&mie); } for (const auto& mie : m_list) { if (mie.type == MFLOW_TARGET) { always_assert(entries.count(mie.target->src) > 0); } } } /* * This method fixes the goto branches when the instruction is removed or * replaced by another instruction. */ void IRList::remove_branch_targets(IRInstruction* branch_inst) { always_assert_log(opcode::is_branch(branch_inst->opcode()), "Instruction is not a branch instruction."); for (auto miter = m_list.begin(); miter != m_list.end(); miter++) { MethodItemEntry* mentry = &*miter; if (mentry->type == MFLOW_TARGET) { BranchTarget* bt = mentry->target; auto btmei = bt->src; if (btmei->insn == branch_inst) { mentry->type = MFLOW_FALLTHROUGH; delete mentry->target; } } } } bool IRList::structural_equals( const IRList& other, const InstructionEquality& instruction_equals) const { auto it1 = m_list.begin(); auto it2 = other.begin(); std::unordered_map<const MethodItemEntry*, const MethodItemEntry*> matches; std::unordered_map<const MethodItemEntry*, const MethodItemEntry*> delayed_matches; auto may_match = [&](const MethodItemEntry* mie1, const MethodItemEntry* mie2) { always_assert(mie1 && mie1->type != MFLOW_DEBUG && mie1->type != MFLOW_POSITION && mie1->type != MFLOW_SOURCE_BLOCK); always_assert(mie2 && mie2->type != MFLOW_DEBUG && mie2->type != MFLOW_POSITION && mie2->type != MFLOW_SOURCE_BLOCK); auto it = matches.find(mie1); if (it != matches.end()) { return it->second == mie2; } auto p = delayed_matches.emplace(mie1, mie2); return p.second || p.first->second == mie2; }; for (; it1 != m_list.end() && it2 != other.end();) { always_assert(it1->type != MFLOW_DEX_OPCODE); always_assert(it2->type != MFLOW_DEX_OPCODE); // Skip debug, position, and source block if (it1->type == MFLOW_DEBUG || it1->type == MFLOW_POSITION || it1->type == MFLOW_SOURCE_BLOCK) { ++it1; continue; } if (it2->type == MFLOW_DEBUG || it2->type == MFLOW_POSITION || it2->type == MFLOW_SOURCE_BLOCK) { ++it2; continue; } if (it1->type != it2->type) { return false; } auto it = delayed_matches.find(&*it1); if (it != delayed_matches.end()) { if (it->second != &*it2) { return false; } delayed_matches.erase(it); } matches.emplace(&*it1, &*it2); if (it1->type == MFLOW_OPCODE) { if (!instruction_equals(*it1->insn, *it2->insn)) { return false; } } else if (it1->type == MFLOW_TARGET) { auto target1 = it1->target; auto target2 = it2->target; if (target1->type != target2->type) { return false; } if (target1->type == BRANCH_MULTI && target1->case_key != target2->case_key) { return false; } // Do these targets point back to the same branch instruction? if (!may_match(target1->src, target2->src)) { return false; } } else if (it1->type == MFLOW_TRY) { auto try1 = it1->tentry; auto try2 = it2->tentry; if (try1->type != try2->type) { return false; } // Do these `try`s correspond to the same catch block? if (!may_match(try1->catch_start, try2->catch_start)) { return false; } } else if (it1->type == MFLOW_CATCH) { auto catch1 = it1->centry; auto catch2 = it2->centry; if (catch1->catch_type != catch2->catch_type) { return false; } if ((catch1->next == nullptr && catch2->next != nullptr) || (catch1->next != nullptr && catch2->next == nullptr)) { return false; } // Do these `catch`es have the same catch after them? if (catch1->next != nullptr && may_match(catch1->next, catch2->next)) { return false; } } ++it1; ++it2; } if (it1 == this->end() && it2 == other.end()) { always_assert(delayed_matches.empty()); return true; } return false; } boost::sub_range<IRList> IRList::get_param_instructions() { auto params_end = std::find_if_not( m_list.begin(), m_list.end(), [&](const MethodItemEntry& mie) { return mie.type == MFLOW_FALLTHROUGH || (mie.type == MFLOW_OPCODE && opcode::is_a_load_param(mie.insn->opcode())); }); return boost::sub_range<IRList>(m_list.begin(), params_end); } void IRList::gather_catch_types(std::vector<DexType*>& ltype) const { for (auto& mie : m_list) { if (mie.type != MFLOW_CATCH) { continue; } if (mie.centry->catch_type != nullptr) { ltype.push_back(mie.centry->catch_type); } } } void IRList::gather_types(std::vector<DexType*>& ltype) const { for (auto& mie : m_list) { mie.gather_types(ltype); } } void IRList::gather_init_classes(std::vector<DexType*>& ltype) const { for (auto& mie : m_list) { mie.gather_init_classes(ltype); } } void IRList::gather_strings(std::vector<const DexString*>& lstring) const { for (auto& mie : m_list) { mie.gather_strings(lstring); } } void IRList::gather_fields(std::vector<DexFieldRef*>& lfield) const { for (auto& mie : m_list) { mie.gather_fields(lfield); } } void IRList::gather_methods(std::vector<DexMethodRef*>& lmethod) const { for (auto& mie : m_list) { mie.gather_methods(lmethod); } } void IRList::gather_callsites(std::vector<DexCallSite*>& lcallsite) const { for (auto& mie : m_list) { mie.gather_callsites(lcallsite); } } void IRList::gather_methodhandles( std::vector<DexMethodHandle*>& lmethodhandle) const { for (auto& mie : m_list) { mie.gather_methodhandles(lmethodhandle); } } IRList::iterator IRList::main_block() { return std::prev(get_param_instructions().end()); } IRList::iterator IRList::make_if_block(const IRList::iterator& cur, IRInstruction* insn, IRList::iterator* false_block) { auto if_entry = new MethodItemEntry(insn); *false_block = m_list.insert(cur, *if_entry); auto bt = new BranchTarget(if_entry); auto bentry = new MethodItemEntry(bt); return m_list.insert(m_list.end(), *bentry); } IRList::iterator IRList::make_if_else_block(const IRList::iterator& cur, IRInstruction* insn, IRList::iterator* false_block, IRList::iterator* true_block) { // if block auto if_entry = new MethodItemEntry(insn); *false_block = m_list.insert(cur, *if_entry); // end of else goto auto goto_entry = new MethodItemEntry(new IRInstruction(OPCODE_GOTO)); auto goto_it = m_list.insert(m_list.end(), *goto_entry); // main block auto main_bt = new BranchTarget(goto_entry); auto mb_entry = new MethodItemEntry(main_bt); auto main_block = m_list.insert(goto_it, *mb_entry); // else block auto else_bt = new BranchTarget(if_entry); auto eb_entry = new MethodItemEntry(else_bt); *true_block = m_list.insert(goto_it, *eb_entry); return main_block; } IRList::iterator IRList::make_switch_block( const IRList::iterator& cur, IRInstruction* insn, IRList::iterator* default_block, std::map<SwitchIndices, IRList::iterator>& cases) { auto switch_entry = new MethodItemEntry(insn); *default_block = m_list.insert(cur, *switch_entry); IRList::iterator main_block = *default_block; for (auto case_it = cases.begin(); case_it != cases.end(); ++case_it) { auto goto_entry = new MethodItemEntry(new IRInstruction(OPCODE_GOTO)); auto goto_it = m_list.insert(m_list.end(), *goto_entry); auto main_bt = new BranchTarget(goto_entry); auto mb_entry = new MethodItemEntry(main_bt); main_block = m_list.insert(++main_block, *mb_entry); // Insert all the branch targets jumping from the switch entry. // Keep updating the iterator of the case block to point right before the // GOTO going back to the end of the switch. for (auto idx : case_it->first) { auto case_bt = new BranchTarget(switch_entry, idx); auto eb_entry = new MethodItemEntry(case_bt); case_it->second = m_list.insert(goto_it, *eb_entry); } } return main_block; } namespace ir_list { 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; } IRInstruction* primary_instruction_of_move_result(IRList::iterator it) { // There may be debug info between primary insn and move-result*? do { --it; } while (it->type != MFLOW_OPCODE); always_assert_log(it->insn->has_move_result(), "%s does not have a move result", SHOW(*it)); return it->insn; } IRInstruction* move_result_pseudo_of(IRList::iterator it) { ++it; always_assert(it->type == MFLOW_OPCODE && opcode::is_a_move_result_pseudo(it->insn->opcode())); return it->insn; } } // namespace ir_list IRList::iterator IRList::insn_erase_and_dispose(const IRList::iterator& it) { return m_list.erase_and_dispose(it, [](auto* mie) { if (mie->type == MFLOW_OPCODE) { delete mie->insn; } delete mie; }); } void IRList::insn_clear_and_dispose() { m_list.clear_and_dispose([](auto* mie) { if (mie->type == MFLOW_OPCODE) { delete mie->insn; } delete mie; }); } std::string SourceBlock::show(bool quoted_src) const { std::ostringstream o; for (const auto* cur = this; cur != nullptr; cur = cur->next.get()) { if (cur != this) { o << " "; } if (quoted_src) { o << "\""; } o << ::show(cur->src); if (quoted_src) { o << "\""; } o << "@" << cur->id; o << "("; for (size_t i = 0; i != cur->vals_size; ++i) { auto& val = cur->vals[i]; if (val) { o << val->val << ":" << val->appear100; } else { o << "x"; } o << "|"; } o << ")"; } return o.str(); }