libredex/SourceBlocks.cpp (902 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 "SourceBlocks.h" #include <limits> #include <optional> #include <sstream> #include <string> #include <unordered_map> #include "ControlFlow.h" #include "Debug.h" #include "DexClass.h" #include "Dominators.h" #include "IROpcode.h" #include "Macros.h" #include "S_Expression.h" #include "ScopedCFG.h" #include "ScopedMetrics.h" #include "Show.h" #include "ShowCFG.h" #include "SourceBlockConsistencyCheck.h" #include "Timer.h" #include "Trace.h" #include "Walkers.h" namespace source_blocks { using namespace cfg; using namespace sparta; namespace { constexpr SourceBlock::Val kFailVal = SourceBlock::Val::none(); constexpr SourceBlock::Val kXVal = SourceBlock::Val::none(); static SourceBlockConsistencyCheck s_sbcc; struct InsertHelper { const DexString* method; uint32_t id{0}; std::ostringstream oss; bool serialize; bool insert_after_excs; struct ProfileParserState { s_expr root_expr; std::vector<s_expr> expr_stack; bool had_profile_failure{false}; boost::optional<SourceBlock::Val> default_val; boost::optional<SourceBlock::Val> error_val; ProfileParserState(s_expr root_expr, std::vector<s_expr> expr_stack, bool had_profile_failure, const boost::optional<SourceBlock::Val>& default_val, const boost::optional<SourceBlock::Val>& error_val) : root_expr(std::move(root_expr)), expr_stack(std::move(expr_stack)), had_profile_failure(had_profile_failure), default_val(default_val), error_val(error_val) {} }; std::vector<ProfileParserState> parser_state; InsertHelper(const DexString* method, const std::vector<ProfileData>& profiles, bool serialize, bool insert_after_excs) : method(method), serialize(serialize), insert_after_excs(insert_after_excs) { for (const auto& p : profiles) { switch (p.index()) { case 0: // Nothing. parser_state.emplace_back(s_expr(), std::vector<s_expr>(), false, boost::none, boost::none); break; case 1: // Profile string. { const auto& pair = std::get<1>(p); const std::string& profile = pair.first; std::istringstream iss{profile}; s_expr_istream s_expr_input(iss); s_expr root_expr; s_expr_input >> root_expr; always_assert_log(!s_expr_input.fail(), "Failed parsing profile %s for %s: %s", profile.c_str(), SHOW(method), s_expr_input.what().c_str()); std::vector<s_expr> expr_stack; expr_stack.push_back(s_expr({root_expr})); parser_state.emplace_back(std::move(root_expr), std::move(expr_stack), false, boost::none, pair.second); break; } case 2: // A default Val. parser_state.emplace_back(s_expr(), std::vector<s_expr>(), false, std::get<2>(p), boost::none); break; default: not_reached(); } } } static SourceBlock::Val parse_val(const std::string& val_str) { if (val_str == "x") { return kXVal; } size_t after_idx; float nested_val = std::stof(val_str, &after_idx); // May throw. always_assert_log(after_idx > 0, "Could not parse first part of %s as float", val_str.c_str()); always_assert_log(after_idx + 1 < val_str.size(), "Could not find separator of %s", val_str.c_str()); always_assert_log(val_str[after_idx] == ':', "Did not find separating ':' in %s", val_str.c_str()); // This isn't efficient, but let's not play with low-level char* view. // Small-string optimization is likely enough. auto appear_part = val_str.substr(after_idx + 1); float appear100 = std::stof(appear_part, &after_idx); always_assert_log(after_idx == appear_part.size(), "Could not parse second part of %s as float", val_str.c_str()); return SourceBlock::Val{nested_val, appear100}; } void start(Block* cur) { if (serialize) { oss << "(" << id; } auto val = start_profile(cur); source_blocks::impl::BlockAccessor::push_source_block( cur, std::make_unique<SourceBlock>(method, id, val)); ++id; if (insert_after_excs) { if (cur->cfg().get_succ_edge_of_type(cur, EdgeType::EDGE_THROW) != nullptr) { // Nothing to do. return; } for (auto it = cur->begin(); it != cur->end(); ++it) { if (it->type != MFLOW_OPCODE) { continue; } if (!opcode::can_throw(it->insn->opcode())) { continue; } // Exclude throws (explicitly). if (it->insn->opcode() == OPCODE_THROW) { continue; } // Get to the next instruction. auto next_it = std::next(it); while (next_it != cur->end() && next_it->type != MFLOW_OPCODE) { ++next_it; } if (next_it == cur->end()) { break; } auto insert_after = opcode::is_move_result_any(next_it->insn->opcode()) ? next_it : it; // This is not really what the structure looks like, but easy to // parse and write. Otherwise, we would need to remember that // we had a nesting. if (serialize) { oss << "(" << id << ")"; } auto nested_val = start_profile(cur, /*empty_inner_tail=*/true); it = source_blocks::impl::BlockAccessor::insert_source_block_after( cur, insert_after, std::make_unique<SourceBlock>(method, id, nested_val)); ++id; } } } std::vector<SourceBlock::Val> start_profile(Block* cur, bool empty_inner_tail = false) { std::vector<SourceBlock::Val> ret; for (auto& p_state : parser_state) { ret.emplace_back(start_profile_one(cur, empty_inner_tail, p_state)); } return ret; } SourceBlock::Val start_profile_one(Block* cur, bool empty_inner_tail, ProfileParserState& p_state) { if (p_state.had_profile_failure) { return kFailVal; } if (p_state.root_expr.is_nil()) { if (p_state.default_val) { return *p_state.default_val; } return kFailVal; } if (p_state.expr_stack.empty()) { p_state.had_profile_failure = true; TRACE(MMINL, 3, "Failed profile matching for %s: missing element for block %zu", SHOW(method), cur->id()); return kFailVal; } std::string val_str; const s_expr& e = p_state.expr_stack.back(); s_expr tail, inner_tail; if (!s_patn({s_patn({s_patn(&val_str)}, inner_tail)}, tail).match_with(e)) { p_state.had_profile_failure = true; TRACE(MMINL, 3, "Failed profile matching for %s: cannot match string for %s", SHOW(method), e.str().c_str()); return kFailVal; } if (empty_inner_tail) { redex_assert(inner_tail.is_nil()); } auto val = parse_val(val_str); TRACE(MMINL, 5, "Started block with val=%f/%f. Popping %s, pushing %s + %s", val ? val->val : std::numeric_limits<float>::quiet_NaN(), val ? val->appear100 : std::numeric_limits<float>::quiet_NaN(), e.str().c_str(), tail.str().c_str(), inner_tail.str().c_str()); p_state.expr_stack.pop_back(); p_state.expr_stack.push_back(tail); if (!empty_inner_tail) { p_state.expr_stack.push_back(inner_tail); } return val; } static char get_edge_char(const Edge* e) { switch (e->type()) { case EDGE_BRANCH: return 'b'; case EDGE_GOTO: return 'g'; case EDGE_THROW: return 't'; case EDGE_GHOST: case EDGE_TYPE_SIZE: not_reached(); } not_reached(); // For GCC. } void edge(Block* cur, const Edge* e) { if (serialize) { oss << " " << get_edge_char(e); } edge_profile(cur, e); } void edge_profile(Block* cur, const Edge* e) { for (auto& p_state : parser_state) { edge_profile_one(cur, e, p_state); } } void edge_profile_one(Block* /*cur*/, const Edge* e, ProfileParserState& p_state) { // If running with profile, there should be at least a nil on. if (p_state.had_profile_failure || p_state.expr_stack.empty()) { return; } std::string val; s_expr& expr = p_state.expr_stack.back(); s_expr tail; if (!s_patn({s_patn(&val)}, tail).match_with(expr)) { p_state.had_profile_failure = true; TRACE(MMINL, 3, "Failed profile matching for %s: cannot match string for %s", SHOW(method), expr.str().c_str()); return; } std::string expected(1, get_edge_char(e)); if (expected != val) { p_state.had_profile_failure = true; TRACE(MMINL, 3, "Failed profile matching for %s: edge type \"%s\" did not match " "expectation \"%s\"", SHOW(method), val.c_str(), expected.c_str()); return; } TRACE(MMINL, 5, "Matched edge %s. Popping %s, pushing %s", val.c_str(), expr.str().c_str(), tail.str().c_str()); p_state.expr_stack.pop_back(); p_state.expr_stack.push_back(tail); } void end(Block* cur) { if (serialize) { oss << ")"; } end_profile(cur); } void end_profile(Block* cur) { for (auto& p_state : parser_state) { end_profile_one(cur, p_state); } } void end_profile_one(Block* /*cur*/, ProfileParserState& p_state) { if (p_state.had_profile_failure) { return; } if (p_state.root_expr.is_nil()) { return; } if (p_state.expr_stack.empty()) { TRACE(MMINL, 3, "Failed profile matching for %s: empty stack on close", SHOW(method)); p_state.had_profile_failure = true; return; } if (!p_state.expr_stack.back().is_nil()) { TRACE(MMINL, 3, "Failed profile matching for %s: edge sentinel not NIL", SHOW(method)); p_state.had_profile_failure = true; return; } TRACE(MMINL, 5, "Popping %s", p_state.expr_stack.back().str().c_str()); p_state.expr_stack.pop_back(); // Remove sentinel nil. } bool wipe_profile_failures(ControlFlowGraph& cfg) { bool ret = false; for (size_t i = 0; i != parser_state.size(); ++i) { auto& p_state = parser_state[i]; if (p_state.root_expr.is_nil()) { continue; } if (!p_state.had_profile_failure) { continue; } ret = true; if (traceEnabled(MMINL, 3) && serialize) { TRACE(MMINL, 3, "For %s, expected profile of the form %s", SHOW(method), oss.str().c_str()); } auto val = p_state.error_val ? *p_state.error_val : SourceBlock::Val::none(); for (auto* b : cfg.blocks()) { auto vec = gather_source_blocks(b); for (auto* sb : vec) { const_cast<SourceBlock*>(sb)->vals[i] = val; } } } return ret; } }; } // namespace SourceBlockConsistencyCheck& get_sbcc() { return s_sbcc; } InsertResult insert_source_blocks(DexMethod* method, ControlFlowGraph* cfg, const std::vector<ProfileData>& profiles, bool serialize, bool insert_after_excs) { InsertHelper helper(&method->get_deobfuscated_name(), profiles, serialize, insert_after_excs); impl::visit_in_order( cfg, [&](Block* cur) { helper.start(cur); }, [&](Block* cur, const Edge* e) { helper.edge(cur, e); }, [&](Block* cur) { helper.end(cur); }); bool had_failures = helper.wipe_profile_failures(*cfg); return {helper.id, helper.oss.str(), !had_failures}; } bool has_source_block_positive_val(const SourceBlock* sb) { bool any_positive_val = false; if (sb != nullptr) { sb->foreach_val_early([&any_positive_val](const auto& val) { any_positive_val = val && val->val > 0.0f; return any_positive_val; }); } return any_positive_val; } namespace { size_t count_blocks( Block*, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { return 1; } size_t count_block_has_sbs( Block* b, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { return source_blocks::has_source_blocks(b) ? 1 : 0; } size_t count_all_sbs( Block* b, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { size_t ret{0}; source_blocks::foreach_source_block( b, [&](auto* sb ATTRIBUTE_UNUSED) { ++ret; }); return ret; } // TODO: Per-interaction stats. size_t hot_immediate_dom_not_hot( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>& dominators) { auto* first_sb_current_b = source_blocks::get_first_source_block(block); if (!has_source_block_positive_val(first_sb_current_b)) { return 0; } auto immediate_dominator = dominators.get_idom(block); if (!immediate_dominator) { return 0; } auto* first_sb_immediate_dominator = source_blocks::get_first_source_block(immediate_dominator); bool is_idom_hot = has_source_block_positive_val(first_sb_immediate_dominator); return is_idom_hot ? 0 : 1; } // TODO: This needs to be adapted to sum up the predecessors. size_t hot_no_hot_pred( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { auto* first_sb_current_b = source_blocks::get_first_source_block(block); if (!has_source_block_positive_val(first_sb_current_b)) { return 0; } for (auto predecessor : block->preds()) { auto* first_sb_pred = source_blocks::get_first_source_block(predecessor->src()); if (has_source_block_positive_val(first_sb_pred)) { return 0; } } return 1; } // TODO: Isn't that the same as before, just this time correct w/ counting? size_t hot_all_pred_cold( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { auto* first_sb_current_b = source_blocks::get_first_source_block(block); if (!has_source_block_positive_val(first_sb_current_b)) { return 0; } for (auto predecessor : block->preds()) { auto* first_sb_pred = source_blocks::get_first_source_block(predecessor->src()); if (has_source_block_positive_val(first_sb_pred)) { return 0; } } return 1; } template <typename Fn> size_t chain_hot_violations_tmpl(Block* block, const Fn& fn) { size_t sum{0}; for (auto& mie : *block) { if (mie.type != MFLOW_SOURCE_BLOCK) { continue; } for (auto* sb = mie.src_block.get(); sb->next != nullptr; sb = sb->next.get()) { // Check that each interaction has at least as high a hit value as the // next SourceBlock. auto* next = sb->next.get(); size_t local_sum{0}; for (size_t i = 0; i != sb->vals_size; ++i) { auto sb_val = sb->get_val(i); auto next_val = next->get_val(i); if (sb_val) { if (next_val && *sb_val < *next_val) { ++local_sum; } } else if (next_val) { ++local_sum; } } sum += fn(local_sum); } } return sum; } size_t chain_hot_violations( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { return chain_hot_violations_tmpl(block, [](auto val) { return val; }); } size_t chain_hot_one_violations( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>&) { return chain_hot_violations_tmpl(block, [](auto val) { return val > 0 ? 1 : 0; }); } struct ChainAndDomState { const SourceBlock* last{nullptr}; cfg::Block* dom_block{nullptr}; size_t violations{0}; }; void chain_and_dom_update( cfg::Block* block, const SourceBlock* sb, bool first_in_block, ChainAndDomState& state, const dominators::SimpleFastDominators<cfg::GraphInterface>& dom) { if (first_in_block) { state.last = nullptr; for (auto* b = dom.get_idom(block); state.last == nullptr && b != nullptr; b = dom.get_idom(b)) { state.last = get_last_source_block(b); if (b == b->cfg().entry_block()) { state.dom_block = b; break; } } } else { state.dom_block = nullptr; } if (state.last != nullptr) { for (size_t i = 0; i != sb->vals_size; ++i) { auto last_val = state.last->get_val(i); auto sb_val = sb->get_val(i); if (last_val) { if (sb_val && *last_val < *sb_val) { state.violations++; break; } } else if (sb_val && *sb_val > 0) { // Treat 'x' and '0' the same for violations for now. state.violations++; break; } } } state.last = sb; } size_t chain_and_dom_violations( Block* block, const dominators::SimpleFastDominators<cfg::GraphInterface>& dom) { ChainAndDomState state{}; bool first = true; foreach_source_block(block, [block, &state, &first, &dom](const auto* sb) { chain_and_dom_update(block, sb, first, state, dom); first = false; }); return state.violations; } // Ugly but necessary for constexpr below. using CounterFnPtr = size_t (*)( Block*, const dominators::SimpleFastDominators<cfg::GraphInterface>&); constexpr std::array<std::pair<std::string_view, CounterFnPtr>, 6> gCounters = { { {"~blocks~count", &count_blocks}, {"~blocks~with~source~blocks", &count_block_has_sbs}, {"~assessment~source~blocks~total", &count_all_sbs}, {"~flow~violation~in~chain", &chain_hot_violations}, {"~flow~violation~in~chain~one", &chain_hot_one_violations}, {"~flow~violation~chain~and~dom", &chain_and_dom_violations}, }}; constexpr std::array<std::pair<std::string_view, CounterFnPtr>, 3> gCountersNonEntry = {{ {"~flow~violation~idom", &hot_immediate_dom_not_hot}, {"~flow~violation~direct~predecessors", &hot_no_hot_pred}, {"~flow~violation~cold~direct~predecessors", &hot_all_pred_cold}, }}; struct SourceBlocksStats { size_t methods_with_sbs{0}; std::array<size_t, gCounters.size()> global{}; std::array<size_t, gCountersNonEntry.size()> non_entry{}; std::array<size_t, gCountersNonEntry.size()> non_entry_methods{}; std::array<std::pair<size_t, size_t>, gCountersNonEntry.size()> non_entry_min_max{}; std::array<std::pair<const DexMethod*, const DexMethod*>, gCountersNonEntry.size()> non_entry_min_max_methods{}; SourceBlocksStats& operator+=(const SourceBlocksStats& that) { methods_with_sbs += that.methods_with_sbs; for (size_t i = 0; i != global.size(); ++i) { global[i] += that.global[i]; } for (size_t i = 0; i != non_entry.size(); ++i) { non_entry[i] += that.non_entry[i]; } for (size_t i = 0; i != non_entry_methods.size(); ++i) { non_entry_methods[i] += that.non_entry_methods[i]; } for (size_t i = 0; i != non_entry_min_max.size(); ++i) { non_entry_min_max[i].first = std::min(non_entry_min_max[i].first, that.non_entry_min_max[i].first); non_entry_min_max[i].second = std::max(non_entry_min_max[i].second, that.non_entry_min_max[i].second); } for (size_t i = 0; i != non_entry_min_max_methods.size(); ++i) { auto set_min_max = [](auto& lhs, auto& rhs, auto fn) { if (rhs == nullptr) { return; } if (lhs == nullptr) { lhs = rhs; return; } auto lhs_count = lhs->get_code()->count_opcodes(); auto rhs_count = rhs->get_code()->count_opcodes(); auto op = fn(lhs_count, rhs_count); if (op == rhs_count && (op != lhs_count || compare_dexmethods(rhs, lhs))) { lhs = rhs; } }; set_min_max(non_entry_min_max_methods[i].first, that.non_entry_min_max_methods[i].first, [](auto lhs, auto rhs) { return std::min(lhs, rhs); }); set_min_max(non_entry_min_max_methods[i].second, that.non_entry_min_max_methods[i].second, [](auto lhs, auto rhs) { return std::max(lhs, rhs); }); } return *this; } void fill_derived(const DexMethod* m) { static_assert(gCounters[1].first == "~blocks~with~source~blocks"); methods_with_sbs = global[1] > 0 ? 1 : 0; for (size_t i = 0; i != non_entry.size(); ++i) { non_entry_methods[i] = non_entry[i] > 0 ? 1 : 0; non_entry_min_max[i].first = non_entry[i]; non_entry_min_max[i].second = non_entry[i]; if (non_entry[i] != 0) { non_entry_min_max_methods[i].first = m; non_entry_min_max_methods[i].second = m; } } } }; } // namespace void track_source_block_coverage(ScopedMetrics& sm, const DexStoresVector& stores) { Timer opt_timer("Calculate SourceBlock Coverage"); auto stats = walk::parallel::methods<SourceBlocksStats>( build_class_scope(stores), [](DexMethod* m) -> SourceBlocksStats { SourceBlocksStats ret; auto code = m->get_code(); if (!code) { return ret; } code->build_cfg(/* editable */ true); auto& cfg = code->cfg(); auto dominators = dominators::SimpleFastDominators<cfg::GraphInterface>(cfg); bool seen_dir_cold_dir_pred = false; bool seen_idom_viol = false; bool seen_direct_pred_viol = false; bool seen_sb = false; for (auto block : cfg.blocks()) { for (size_t i = 0; i != gCounters.size(); ++i) { ret.global[i] += (*gCounters[i].second)(block, dominators); } if (block != cfg.entry_block()) { for (size_t i = 0; i != gCountersNonEntry.size(); ++i) { ret.non_entry[i] += (*gCountersNonEntry[i].second)(block, dominators); } } } ret.fill_derived(m); code->clear_cfg(); return ret; }); size_t consistency_check_violations = s_sbcc.is_initialized() ? s_sbcc.run(build_class_scope(stores)) : 0; sm.set_metric("~consistency~check~violations", consistency_check_violations); sm.set_metric("~assessment~methods~with~sbs", stats.methods_with_sbs); for (size_t i = 0; i != gCounters.size(); ++i) { sm.set_metric(gCounters[i].first, stats.global[i]); } for (size_t i = 0; i != gCountersNonEntry.size(); ++i) { sm.set_metric(gCountersNonEntry[i].first, stats.non_entry[i]); auto scope = sm.scope(std::string(gCountersNonEntry[i].first)); sm.set_metric("methods", stats.non_entry_methods[i]); sm.set_metric("min", stats.non_entry_min_max[i].first); sm.set_metric("max", stats.non_entry_min_max[i].second); auto min_max = [&](const DexMethod* m, const char* name) { if (m != nullptr) { auto min_max_scope = sm.scope(name); sm.set_metric(show_deobfuscated(m), m->get_code()->count_opcodes()); } }; min_max(stats.non_entry_min_max_methods[i].first, "min_method"); min_max(stats.non_entry_min_max_methods[i].second, "max_method"); } } struct ViolationsHelper::ViolationsHelperImpl { std::unordered_map<DexMethod*, size_t> violations_start; std::vector<std::string> print; using Violation = ViolationsHelper::Violation; const Violation v; ViolationsHelperImpl(Violation v, const Scope& scope, std::vector<std::string> to_vis) : print(std::move(to_vis)), v(v) { { std::mutex lock; walk::parallel::methods(scope, [this, &lock, v](DexMethod* m) { if (m->get_code() == nullptr) { return; } cfg::ScopedCFG cfg(m->get_code()); auto val = compute(v, *cfg); { std::unique_lock<std::mutex> ulock{lock}; violations_start[m] = val; } }); } print_all(); } static size_t compute(Violation v, cfg::ControlFlowGraph& cfg) { switch (v) { case Violation::kHotImmediateDomNotHot: return hot_immediate_dom_not_hot_cfg(cfg); case Violation::kChainAndDom: return chain_and_dom_violations_cfg(cfg); } not_reached(); } ~ViolationsHelperImpl() { std::atomic<size_t> change_sum{0}; { std::mutex lock; struct MethodDelta { DexMethod* method; size_t violations_delta; size_t method_size; MethodDelta(DexMethod* p1, size_t p2, size_t p3) : method(p1), violations_delta(p2), method_size(p3) {} }; std::vector<MethodDelta> top_changes; constexpr size_t kTopChanges = 10; workqueue_run<std::pair<DexMethod*, size_t>>( [&](const std::pair<DexMethod*, size_t>& p) { auto* m = p.first; if (m->get_code() == nullptr) { return; } cfg::ScopedCFG cfg(m->get_code()); auto val = compute(v, *cfg); if (val <= p.second) { return; } change_sum.fetch_add(val - p.second); auto m_delta = val - p.second; size_t s = m->get_code()->sum_opcode_sizes(); std::unique_lock<std::mutex> ulock{lock}; if (top_changes.size() < kTopChanges) { top_changes.emplace_back(m, m_delta, s); return; } MethodDelta m_t{m, m_delta, s}; auto cmp = [](const auto& t1, const auto& t2) { if (t1.violations_delta > t2.violations_delta) { return true; } if (t1.violations_delta < t2.violations_delta) { return false; } if (t1.method_size < t2.method_size) { return true; } if (t1.method_size > t2.method_size) { return false; } return compare_dexmethods(t1.method, t2.method); }; if (cmp(m_t, top_changes.back())) { top_changes.back() = m_t; std::sort(top_changes.begin(), top_changes.end(), cmp); } }, violations_start); for (auto& t : top_changes) { TRACE(MMINL, 0, "%s (size %zu): +%zu", SHOW(t.method), t.method_size, t.violations_delta); } } print_all(); TRACE(MMINL, 0, "Introduced %zu violations.", change_sum.load()); } static size_t hot_immediate_dom_not_hot_cfg(cfg::ControlFlowGraph& cfg) { size_t sum{0}; dominators::SimpleFastDominators<cfg::GraphInterface> dom{cfg}; for (auto* b : cfg.blocks()) { sum += hot_immediate_dom_not_hot(b, dom); } return sum; } static size_t chain_and_dom_violations_cfg(cfg::ControlFlowGraph& cfg) { size_t sum{0}; dominators::SimpleFastDominators<cfg::GraphInterface> dom{cfg}; for (auto* b : cfg.blocks()) { sum += chain_and_dom_violations(b, dom); } return sum; } void print_all() const { for (const auto& m_str : print) { auto* m = DexMethod::get_method(m_str); if (m != nullptr) { redex_assert(m != nullptr && m->is_def()); auto* m_def = m->as_def(); print_cfg_with_violations(v, m_def); } } } template <typename SpecialT> static void print_cfg_with_violations(DexMethod* m) { cfg::ScopedCFG cfg(m->get_code()); SpecialT special{*cfg}; TRACE(MMINL, 0, "=== %s ===\n%s\n", SHOW(m), show<SpecialT>(*cfg, special).c_str()); } static void print_cfg_with_violations(Violation v, DexMethod* m) { switch (v) { case Violation::kHotImmediateDomNotHot: { struct HotImmediateSpecial { cfg::Block* cur{nullptr}; dominators::SimpleFastDominators<cfg::GraphInterface> dom; explicit HotImmediateSpecial(cfg::ControlFlowGraph& cfg) : dom(dominators::SimpleFastDominators<cfg::GraphInterface>(cfg)) {} void mie_before(std::ostream&, const MethodItemEntry&) {} void mie_after(std::ostream& os, const MethodItemEntry& mie) { if (mie.type != MFLOW_SOURCE_BLOCK) { return; } auto immediate_dominator = dom.get_idom(cur); if (!immediate_dominator) { os << " NO DOMINATOR\n"; return; } if (!source_blocks::has_source_block_positive_val( mie.src_block.get())) { os << " NOT HOT\n"; return; } auto* first_sb_immediate_dominator = source_blocks::get_first_source_block(immediate_dominator); if (!first_sb_immediate_dominator) { os << " NO DOMINATOR SOURCE BLOCK B" << immediate_dominator->id() << "\n"; return; } bool is_idom_hot = source_blocks::has_source_block_positive_val( first_sb_immediate_dominator); if (is_idom_hot) { os << " DOMINATOR HOT\n"; return; } os << " !!! B" << immediate_dominator->id() << ": "; auto sb = first_sb_immediate_dominator; os << " \"" << show(sb->src) << "\"@" << sb->id; for (size_t i = 0; i < sb->vals_size; i++) { auto& val = sb->vals[i]; os << " "; if (val) { os << val->val << "/" << val->appear100; } else { os << "N/A"; } } os << "\n"; } void start_block(std::ostream&, cfg::Block* b) { cur = b; } void end_block(std::ostream&, cfg::Block*) { cur = nullptr; } }; print_cfg_with_violations<HotImmediateSpecial>(m); return; } case Violation::kChainAndDom: { struct ChainAndDom { cfg::Block* cur{nullptr}; ChainAndDomState state{}; bool first_in_block{false}; dominators::SimpleFastDominators<cfg::GraphInterface> dom; explicit ChainAndDom(cfg::ControlFlowGraph& cfg) : dom(dominators::SimpleFastDominators<cfg::GraphInterface>(cfg)) {} void mie_before(std::ostream&, const MethodItemEntry&) {} void mie_after(std::ostream& os, const MethodItemEntry& mie) { if (mie.type != MFLOW_SOURCE_BLOCK) { return; } size_t old_count = state.violations; auto* sb = mie.src_block.get(); chain_and_dom_update(cur, sb, first_in_block, state, dom); first_in_block = false; const bool head_error = state.violations > old_count; const auto* dom_block = state.dom_block; for (auto* cur_sb = sb->next.get(); cur_sb != nullptr; cur_sb = cur_sb->next.get()) { chain_and_dom_update(cur, cur_sb, false, state, dom); } if (state.violations > old_count) { os << " !!!"; if (head_error) { os << " HEAD"; if (dom_block != nullptr) { os << " (B" << dom_block->id() << ")"; } } auto other = state.violations - old_count - (head_error ? 1 : 0); if (other > 0) { os << " IN_CHAIN " << other; } os << "\n"; } } void start_block(std::ostream&, cfg::Block* b) { cur = b; first_in_block = true; } void end_block(std::ostream&, cfg::Block*) { cur = nullptr; } }; print_cfg_with_violations<ChainAndDom>(m); return; } } not_reached(); } }; ViolationsHelper::ViolationsHelper(Violation v, const Scope& scope, std::vector<std::string> to_vis) : impl(std::make_unique<ViolationsHelperImpl>( v, scope, std::move(to_vis))) {} ViolationsHelper::~ViolationsHelper() {} } // namespace source_blocks