Exemple #1
0
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;
}
Exemple #2
0
 void delete_unused_bridgees() {
   for (auto bpair : m_bridges_to_bridgees) {
     auto bridge = bpair.first;
     auto bridgee = bpair.second;
     always_assert_log(bridge->is_virtual(),
                       "bridge: %s\nbridgee: %s",
                       SHOW(bridge),
                       SHOW(bridgee));
     // TODO: Bridgee won't necessarily be direct once we expand this
     // optimization
     assert(!bridgee->is_virtual());
     auto cls = type_class(bridgee->get_class());
     cls->get_dmethods().remove(bridgee);
   }
 }
Exemple #3
0
void StaticSinkPass::run_pass(DexClassesVector& dexen, ConfigFiles& cfg) {
  auto method_list = cfg.get_coldstart_methods();
  auto methods = strings_to_dexmethods(method_list);
  TRACE(SINK, 1, "methods used in coldstart: %lu\n", methods.size());
  auto coldstart_classes = get_coldstart_classes(dexen, cfg);
  count_coldstart_statics(coldstart_classes);
  auto statics = get_noncoldstart_statics(coldstart_classes, methods);
  TRACE(SINK, 1, "statics not used in coldstart: %lu\n", statics.size());
  remove_primary_dex_refs(dexen[0], statics);
  TRACE(SINK, 1, "statics after removing primary dex: %lu\n", statics.size());
  auto sink_map = get_sink_map(dexen, coldstart_classes, statics);
  TRACE(SINK, 1, "statics with sinkable callsite: %lu\n", sink_map.size());
  auto holder = move_statics_out(statics, sink_map);
  TRACE(SINK, 1, "methods in static holder: %lu\n",
          holder->get_dmethods().size());
  DexClasses dc(1);
  dc.insert_at(holder, 0);
  dexen.emplace_back(std::move(dc));
}
Exemple #4
0
TEST_F(PreVerify, InlineInvokeDirect) {
  auto cls = find_class_named(
    classes, "Lcom/facebook/redexinline/InlineTest;");
  auto m = find_vmethod_named(*cls, "testInlineInvokeDirect");
  auto invoke =
      find_invoke(m, OPCODE_INVOKE_DIRECT, "hasNoninlinableInvokeDirect");
  auto noninlinable_invoke_direct =
      find_invoke(invoke->get_method(), OPCODE_INVOKE_DIRECT, "noninlinable");
  auto noninlinable = noninlinable_invoke_direct->get_method();
  ASSERT_EQ(show(noninlinable->get_proto()), "()V");

  // verify that there are two inlinable() methods in the class. The static
  // version exists to test that we don't cause a signature collision when we
  // make the instance method static.
  auto dmethods = cls->get_dmethods();
  ASSERT_EQ(2,
            std::count_if(dmethods.begin(), dmethods.end(), [](DexMethod* m) {
              return !strcmp("noninlinable", m->get_name()->c_str());
            }));
}
Exemple #5
0
TEST_F(PostVerify, InlineInvokeDirect) {
  auto cls = find_class_named(
    classes, "Lcom/facebook/redexinline/InlineTest;");
  auto m = find_vmethod_named(*cls, "testInlineInvokeDirect");
  auto noninlinable_invoke_direct =
      find_invoke(m, OPCODE_INVOKE_STATIC, "noninlinable$redex0");
  ASSERT_NE(nullptr, noninlinable_invoke_direct);
  auto noninlinable = noninlinable_invoke_direct->get_method();
  ASSERT_EQ(show(noninlinable->get_proto()),
            "(Lcom/facebook/redexinline/InlineTest;)V");

  auto dmethods = cls->get_dmethods();
  // verify that we've replaced the instance noninlinable() method with
  // noninlinable$redex
  ASSERT_EQ(1,
            std::count_if(dmethods.begin(), dmethods.end(), [](DexMethod* m) {
              return !strcmp("noninlinable", m->get_name()->c_str());
            }));
  ASSERT_EQ(1,
            std::count_if(dmethods.begin(), dmethods.end(), [](DexMethod* m) {
              return !strcmp("noninlinable$redex0", m->get_name()->c_str());
            }));
}
Exemple #6
0
void RemoveBuildersPass::run_pass(DexStoresVector& stores,
                                  ConfigFiles&,
                                  PassManager& mgr) {
  if (mgr.no_proguard_rules()) {
    TRACE(BUILDERS,
          1,
          "RemoveBuildersPass did not run because no Proguard configuration "
          "was provided.");
    return;
  }

  // Initialize couters.
  b_counter = {0, 0, 0, 0};

  auto obj_type = get_object_type();
  auto scope = build_class_scope(stores);
  for (DexClass* cls : scope) {
    if (is_annotation(cls) || is_interface(cls) ||
        cls->get_super_class() != obj_type) {
      continue;
    }

    if (has_builder_name(cls->get_type())) {
      m_builders.emplace(cls->get_type());
    }
  }

  std::unordered_set<DexType*> escaped_builders;
  walk::methods(scope, [&](DexMethod* m) {
    auto builders = created_builders(m);
    for (DexType* builder : builders) {
      if (escapes_stack(builder, m)) {
        TRACE(BUILDERS,
              3,
              "%s escapes in %s\n",
              SHOW(builder),
              m->get_deobfuscated_name().c_str());
        escaped_builders.emplace(builder);
      }
    }
  });

  std::unordered_set<DexType*> stack_only_builders;
  for (DexType* builder : m_builders) {
    if (escaped_builders.find(builder) == escaped_builders.end()) {
      stack_only_builders.emplace(builder);
    }
  }

  std::unordered_set<DexType*> builders_and_supers;
  for (DexType* builder : stack_only_builders) {
    DexType* cls = builder;
    while (cls != nullptr && cls != obj_type) {
      builders_and_supers.emplace(cls);
      cls = type_class(cls)->get_super_class();
    }
  }

  std::unordered_set<DexType*> this_escapes;
  for (DexType* cls_ty : builders_and_supers) {
    DexClass* cls = type_class(cls_ty);
    if (cls->is_external() ||
        this_arg_escapes(cls, m_enable_buildee_constr_change)) {
      this_escapes.emplace(cls_ty);
    }
  }

  // set of builders that neither escape the stack nor pass their 'this' arg
  // to another function
  std::unordered_set<DexType*> no_escapes;
  for (DexType* builder : stack_only_builders) {
    DexType* cls = builder;
    bool hierarchy_has_escape = false;
    while (cls != nullptr) {
      if (this_escapes.find(cls) != this_escapes.end()) {
        hierarchy_has_escape = true;
        break;
      }
      cls = type_class(cls)->get_super_class();
    }
    if (!hierarchy_has_escape) {
      no_escapes.emplace(builder);
    }
  }

  size_t dmethod_count = 0;
  size_t vmethod_count = 0;
  size_t build_count = 0;
  for (DexType* builder : no_escapes) {
    auto cls = type_class(builder);
    auto buildee = get_buildee(builder);
    dmethod_count += cls->get_dmethods().size();
    vmethod_count += cls->get_vmethods().size();
    for (DexMethod* m : cls->get_vmethods()) {
      if (m->get_proto()->get_rtype() == buildee) {
        build_count++;
      }
    }
  }

  std::unordered_set<DexClass*> trivial_builders =
      get_trivial_builders(m_builders, no_escapes);

  std::unordered_set<DexClass*> kept_builders =
      get_builders_with_subclasses(scope);

  PassConfig pc(mgr.get_config());
  BuilderTransform b_transform(pc, scope, stores, false);

  // Inline non init methods.
  std::unordered_set<DexClass*> removed_builders;
  walk::methods(scope, [&](DexMethod* method) {
    auto builders = created_builders(method);

    for (DexType* builder : builders) {
      if (method->get_class() == builder) {
        continue;
      }

      DexClass* builder_cls = type_class(builder);

      // Filter out builders that we cannot remove.
      if (kept_builders.find(builder_cls) != kept_builders.end()) {
        continue;
      }

      // Check it is a trivial one.
      if (trivial_builders.find(builder_cls) != trivial_builders.end()) {

        DexMethod* method_copy = DexMethod::make_method_from(
            method,
            method->get_class(),
            DexString::make_string(method->get_name()->str() +
                                   "$redex_builders"));
        bool was_not_removed =
            !b_transform.inline_methods(
                method, builder, &get_non_init_methods) ||
            !remove_builder_from(method, builder_cls, b_transform);

        if (was_not_removed) {
          kept_builders.emplace(builder_cls);
          method->set_code(method_copy->release_code());
        } else {
          b_counter.methods_cleared++;
          removed_builders.emplace(builder_cls);
        }

        DexMethod::erase_method(method_copy);
      }
    }
  });

  // No need to remove the builders here, since `RemoveUnreachable` will
  // take care of it.
  gather_removal_builder_stats(removed_builders, kept_builders);

  mgr.set_metric("total_builders", m_builders.size());
  mgr.set_metric("stack_only_builders", stack_only_builders.size());
  mgr.set_metric("no_escapes", no_escapes.size());
  mgr.incr_metric(METRIC_CLASSES_REMOVED, b_counter.classes_removed);
  mgr.incr_metric(METRIC_METHODS_REMOVED, b_counter.methods_removed);
  mgr.incr_metric(METRIC_FIELDS_REMOVED, b_counter.fields_removed);
  mgr.incr_metric(METRIC_METHODS_CLEARED, b_counter.methods_cleared);

  TRACE(BUILDERS, 1, "Total builders: %d\n", m_builders.size());
  TRACE(BUILDERS, 1, "Stack-only builders: %d\n", stack_only_builders.size());
  TRACE(BUILDERS,
        1,
        "Stack-only builders that don't let `this` escape: %d\n",
        no_escapes.size());
  TRACE(BUILDERS, 1, "Stats for unescaping builders:\n");
  TRACE(BUILDERS, 1, "\tdmethods: %d\n", dmethod_count);
  TRACE(BUILDERS, 1, "\tvmethods: %d\n", vmethod_count);
  TRACE(BUILDERS, 1, "\tbuild methods: %d\n", build_count);
  TRACE(BUILDERS, 1, "Trivial builders: %d\n", trivial_builders.size());
  TRACE(BUILDERS, 1, "Classes removed: %d\n", b_counter.classes_removed);
  TRACE(BUILDERS, 1, "Methods removed: %d\n", b_counter.methods_removed);
  TRACE(BUILDERS, 1, "Fields removed: %d\n", b_counter.fields_removed);
  TRACE(BUILDERS, 1, "Methods cleared: %d\n", b_counter.methods_cleared);
}