Beispiel #1
0
void DexInstruction::verify_encoding() const {
  auto test = m_count ? new DexInstruction(opcode()) : new DexInstruction(opcode(), 0);
  if (dests_size()) {
    test->set_dest(dest());
  }
  for (unsigned i = 0; i < srcs_size(); i++) {
    test->set_src(i, src(i));
  }
  if (has_range_base()) test->set_range_base(range_base());
  if (has_range_size()) test->set_range_size(range_size());
  if (has_arg_word_count()) test->set_arg_word_count(arg_word_count());
  if (has_literal()) test->set_literal(literal());
  if (has_offset()) test->set_offset(offset());

  assert_log(m_opcode == test->m_opcode, "%x %x\n", m_opcode, test->m_opcode);
  for (unsigned i = 0; i < m_count; i++) {
    assert_log(m_arg[i] == test->m_arg[i],
               "(%x %x) (%x %x)",
               m_opcode,
               m_arg[i],
               test->m_opcode,
               test->m_arg[i]);
  }

  delete test;
}
Beispiel #2
0
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) {
  auto const locId = inst->extra<AssertLoc>()->locId;

  auto const prevType = localType(locId, DataTypeGeneric);
  auto const typeParam = inst->typeParam();

  if (prevType.not(typeParam)) {
    /* Task #2553746
     * This is triggering for a case where the tracked state says the local is
     * InitNull but the AssertLoc says it's Str. */
    static auto const error =
      makeStaticString("Internal error: static analysis was "
                       "wrong about a local variable's type.");
    auto* errorInst = m_unit.gen(RaiseError, inst->marker(), cns(error));
    inst->become(m_unit, errorInst);

    // It's not a disaster to generate this in unreachable code for
    // now. t2590033.
    if (false) {
      assert_log(false,  [&]{
          return folly::format("\npreOptimizeAssertLoc: prevType: {} "
                               "typeParam: {}\nin instr: {}\nin trace: {}\n",
                               prevType.toString(),
                               typeParam.toString(),
                               inst->toString(),
                               m_unit.main()->toString()).str();
        });
    }
  } else if (shouldElideAssertType(prevType, typeParam, nullptr)) {
    // The type we're asserting is worse than the last known type.
    return inst->src(0);
  }
  return nullptr;
}
Beispiel #3
0
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) {
  auto const locId = inst->extra<AssertLoc>()->locId;
  auto const prevType = localType(locId, DataTypeGeneric);
  auto const typeParam = inst->typeParam();

  if (!prevType.equals(Type::None) && !typeParam.strictSubtypeOf(prevType)) {
    if (!prevType.subtypeOf(typeParam)) {
      /* Task #2553746
       * This is triggering for a case where the tracked state says the local is
       * InitNull but the AssertLoc says it's Str. */
      static auto const error =
        StringData::GetStaticString("Internal error: static analysis was "
                                    "wrong about a local variable's type.");
      auto* errorInst = m_irFactory.gen(RaiseError, inst->marker(), cns(error));
      inst->become(&m_irFactory, errorInst);

      // It's not a disaster to generate this in unreachable code for
      // now. t2590033.
      if (false) {
        assert_log(false,  [&]{
            IRTrace& mainTrace = trace()->isMain() ? *trace()
                                                   : *(trace()->main());
            return folly::format("\npreOptimizeAssertLoc: prevType: {} "
                                 "typeParam: {}\nin instr: {}\nin trace: {}\n",
                                 prevType.toString(), typeParam.toString(),
                                 inst->toString(), mainTrace.toString()).str();
          });
      }
    } else {
      inst->convertToNop();
    }
  }
  return nullptr;
}
Beispiel #4
0
void ConfigFiles::load_method_to_weight() {
  std::ifstream infile(m_profiled_methods_filename.c_str());
  assert_log(infile, "Can't open method profile file: %s\n",
             m_profiled_methods_filename.c_str());

  std::string deobfuscated_name;
  unsigned int weight;
  TRACE(CUSTOMSORT, 2, "Setting sort start file %s\n",
        m_profiled_methods_filename.c_str());

  unsigned int count = 0;
  while (infile >> deobfuscated_name >> weight) {
    m_method_to_weight[deobfuscated_name] = weight;
    count++;
  }

  assert_log(count > 0, "Method profile file %s didn't contain valid entries\n",
             m_profiled_methods_filename.c_str());
  TRACE(CUSTOMSORT, 2, "Preset sort weight count=%d\n", count);
}
Beispiel #5
0
/*
 * Build the CFG, then the dominator tree, then use it to validate SSA.
 * 1. Each src must be defined by some other instruction, and each dst must
 *    be defined by the current instruction.
 * 2. Each src must be defined earlier in the same block or in a dominator.
 * 3. Each dst must not be previously defined.
 * 4. Treat tmps defined by DefConst as always defined.
 * 5. Each predecessor of a reachable block must be reachable (deleted
 *    blocks must not have out-edges to reachable blocks).
 * 6. The entry block must not have any predecessors.
 */
bool checkCfg(const IRUnit& unit) {
  auto const blocksIds = rpoSortCfgWithIds(unit);
  auto const& blocks = blocksIds.blocks;
  jit::hash_set<const Edge*> edges;

  // Entry block can't have predecessors.
  assert(unit.entry()->numPreds() == 0);

  // Check valid successor/predecessor edges.
  for (Block* b : blocks) {
    auto checkEdge = [&] (const Edge* e) {
      assert(e->from() == b);
      edges.insert(e);
      for (auto& p : e->to()->preds()) if (&p == e) return;
      assert(false); // did not find edge.
    };
    checkBlock(b);
    if (auto *e = b->nextEdge())  checkEdge(e);
    if (auto *e = b->takenEdge()) checkEdge(e);
  }
  for (Block* b : blocks) {
    for (DEBUG_ONLY auto const &e : b->preds()) {
      assert(&e == e.inst()->takenEdge() || &e == e.inst()->nextEdge());
      assert(e.to() == b);
    }
  }

  // visit dom tree in preorder, checking all tmps
  auto const children = findDomChildren(unit, blocksIds);
  StateVector<SSATmp, bool> defined0(unit, false);
  forPreorderDoms(blocks.front(), children, defined0,
                  [] (Block* block, StateVector<SSATmp, bool>& defined) {
    for (IRInstruction& inst : *block) {
      for (DEBUG_ONLY SSATmp* src : inst.srcs()) {
        assert(src->inst() != &inst);
        assert_log(src->inst()->op() == DefConst ||
                   defined[src],
                   [&]{ return folly::format(
                       "src '{}' in '{}' came from '{}', which is not a "
                       "DefConst and is not defined at this use site",
                       src->toString(), inst.toString(),
                       src->inst()->toString()).str();
                   });
      }
      for (SSATmp& dst : inst.dsts()) {
        assert(dst.inst() == &inst && inst.op() != DefConst);
        assert(!defined[dst]);
        defined[dst] = true;
      }
    }
  });
  return true;
}
Beispiel #6
0
/*
 * Build the CFG, then the dominator tree, then use it to validate SSA.
 * 1. Each src must be defined by some other instruction, and each dst must
 *    be defined by the current instruction.
 * 2. Each src must be defined earlier in the same block or in a dominator.
 * 3. Each dst must not be previously defined.
 * 4. Treat tmps defined by DefConst as always defined.
 * 5. Each predecessor of a reachable block must be reachable (deleted
 *    blocks must not have out-edges to reachable blocks).
 */
bool checkCfg(IRTrace* trace, const IRFactory& factory) {
  forEachTraceBlock(trace, checkBlock);

  // Check valid successor/predecessor edges.
  auto const blocks = rpoSortCfg(trace, factory);
  std::unordered_set<const Edge*> edges;
  for (Block* b : blocks) {
    auto checkEdge = [&] (const Edge* e) {
      assert(e->from() == b);
      edges.insert(e);
      for (auto& p : e->to()->preds()) if (&p == e) return;
      assert(false); // did not find edge.
    };
    if (auto *e = nextEdge(b))  checkEdge(e);
    if (auto *e = takenEdge(b)) checkEdge(e);
  }
  for (Block* b : blocks) {
    for (DEBUG_ONLY auto const &e : b->preds()) {
      assert(&e == takenEdge(e.from()) || &e == nextEdge(e.from()));
      assert(e.to() == b);
    }
  }

  checkCatchTraces(trace, factory);

  // visit dom tree in preorder, checking all tmps
  auto const children = findDomChildren(blocks);
  StateVector<SSATmp, bool> defined0(&factory, false);
  forPreorderDoms(blocks.front(), children, defined0,
                  [] (Block* block, StateVector<SSATmp, bool>& defined) {
    for (IRInstruction& inst : *block) {
      for (DEBUG_ONLY SSATmp* src : inst.srcs()) {
        assert(src->inst() != &inst);
        assert_log(src->inst()->op() == DefConst ||
                   defined[src],
                   [&]{ return folly::format(
                       "src '{}' in '{}' came from '{}', which is not a "
                       "DefConst and is not defined at this use site",
                       src->toString(), inst.toString(),
                       src->inst()->toString()).str();
                   });
      }
      for (SSATmp& dst : inst.dsts()) {
        assert(dst.inst() == &inst && inst.op() != DefConst);
        assert(!defined[dst]);
        defined[dst] = true;
      }
    }
  });
  return true;
}
Beispiel #7
0
/*
 * Build the interference graph by adding edges between nodes that are
 * simultaneously live.
 *
 * check-cast instructions have to be handled specially. They are represented
 * with both a dest (via a move-result-pseudo) and a src in our IR. However, in
 * actual Dex bytecode, it only takes a single operand which acts as both src
 * and dest. So when converting IR to Dex bytecode, we need to insert a move
 * instruction if the src and dest operands differ. We must insert the move
 * before, not after, the check-cast. Suppose we did not:
 *
 *        IR                  |           Dex
 *   sget-object v0 LFoo;     |  sget-object v0 LFoo;
 *   check-cast v0 LBar;      |  check-cast v0 LBar;
 *   move-result-pseudo v1    |  move-object v1 v0
 *   invoke-static v0 LFoo.a; |  invoke-static v0 LFoo.a; // v0 is of type Bar!
 *
 * However, inserting before the check-cast is tricky to get right. If the
 * check-cast is in a try region, we must be careful to not clobber other
 * live registers. For example, if we had some IRCode like
 *
 *   B0:
 *     load-param v1 Ljava/lang/Object;
 *     TRY_START
 *     const v0 123
 *     check-cast v1 LFoo;
 *   B1:
 *     move-result-pseudo v0
 *     return v0
 *     TRY_END
 *   B2:
 *     CATCH
 *     // handle failure of check-cast
 *     // Note that v0 has the value of 123 here because the check-cast failed
 *     add-int v0, v0, v0
 *
 * Inserting the move before the check-cast would cause v0 to have an object
 * (instead of integer) type inside the exception handler.
 *
 * The solution is to have the interference graph make check-cast's dest
 * register interfere with the live registers in both B0 and B1, so that when
 * the move gets inserted, it does not clobber any live registers.
 */
Graph GraphBuilder::build(const LivenessFixpointIterator& fixpoint_iter,
                          IRCode* code,
                          reg_t initial_regs,
                          const RangeSet& range_set) {
  Graph graph;
  auto ii = InstructionIterable(code);
  for (auto it = ii.begin(); it != ii.end(); ++it) {
    GraphBuilder::update_node_constraints(it.unwrap(), range_set, &graph);
  }

  auto& cfg = code->cfg();
  for (cfg::Block* block : cfg.blocks()) {
    LivenessDomain live_out = fixpoint_iter.get_live_out_vars_at(block);
    for (auto it = block->rbegin(); it != block->rend(); ++it) {
      if (it->type != MFLOW_OPCODE) {
        continue;
      }
      auto insn = it->insn;
      auto op = insn->opcode();
      if (opcode::has_range_form(op)) {
        graph.m_range_liveness.emplace(insn, live_out);
      }
      if (insn->dests_size()) {
        for (auto reg : live_out.elements()) {
          if (is_move(op) && reg == insn->src(0)) {
            continue;
          }
          graph.add_edge(insn->dest(), reg);
        }
        // We add interference edges between the wide src and dest operands of
        // an instruction even if the srcs are not live-out. This avoids
        // allocations like `xor-long v1, v0, v9`, where v1 and v0 overlap --
        // even though this is not a verification error, we have observed bugs
        // in the ART interpreter when handling these sorts of instructions.
        // However, we still want to be able to coalesce these symregs if they
        // don't actually interfere based on liveness information, so that we
        // can remove move-wide opcodes and/or use /2addr encodings.  As such,
        // we insert a specially marked edge that coalescing ignores but
        // coloring respects.
        if (insn->dest_is_wide()) {
          for (size_t i = 0; i < insn->srcs_size(); ++i) {
            if (insn->src_is_wide(i)) {
              graph.add_coalesceable_edge(insn->dest(), insn->src(i));
            }
          }
        }
      }
      if (op == OPCODE_CHECK_CAST) {
        auto move_result_pseudo = std::prev(it)->insn;
        for (auto reg : live_out.elements()) {
          graph.add_edge(move_result_pseudo->dest(), reg);
        }
      }
      // adding containment edge between liverange defined in insn and elements
      // in live-out set of insn
      if (insn->dests_size()) {
        for (auto reg : live_out.elements()) {
          graph.add_containment_edge(insn->dest(), reg);
        }
      }
      fixpoint_iter.analyze_instruction(it->insn, &live_out);
      // adding containment edge between liverange used in insn and elements
      // in live-in set of insn
      for (size_t i = 0; i < insn->srcs_size(); ++i) {
        for (auto reg : live_out.elements()) {
          graph.add_containment_edge(insn->src(i), reg);
        }
      }
    }
  }
  for (auto& pair : graph.nodes()) {
    auto reg = pair.first;
    auto& node = pair.second;
    if (reg >= initial_regs) {
      node.m_props.set(Node::SPILL);
    }
    assert_log(!node.m_type_domain.is_bottom(),
               "Type violation of v%u in code:\n%s\n",
               reg,
               SHOW(code));
  }
  return graph;
}