Example #1
0
void SingleImplPass::run_pass(DexStoresVector& stores, ConfigFiles& cfg, PassManager& mgr) {
  auto scope = build_class_scope(stores);
  ClassHierarchy ch = build_type_hierarchy(scope);
  int max_steps = 0;
  size_t previous_invoke_intf_count = s_invoke_intf_count;
  removed_count = 0;
  while (true) {
    DEBUG_ONLY size_t scope_size = scope.size();
    TypeToTypes intfs_to_classes;
    TypeSet intfs;
    build_type_maps(scope, intfs_to_classes, intfs);
    TypeMap single_impl;
    collect_single_impl(intfs_to_classes, single_impl);

    std::unique_ptr<SingleImplAnalysis> single_impls =
        SingleImplAnalysis::analyze(
            scope, stores, single_impl, intfs, m_pass_config);
    auto optimized = optimize(
        std::move(single_impls), ch, scope, m_pass_config);
    if (optimized == 0 || ++max_steps >= MAX_PASSES) break;
    removed_count += optimized;
    assert(scope_size > scope.size());
  }

  TRACE(INTF, 1, "Removed interfaces %ld\n", removed_count);
  TRACE(INTF, 1,
          "Updated invoke-interface to invoke-virtual %ld\n",
          s_invoke_intf_count - previous_invoke_intf_count);

  mgr.incr_metric(METRIC_REMOVED_INTERFACES, removed_count);
  mgr.incr_metric(METRIC_INVOKE_INT_TO_VIRT,
                  s_invoke_intf_count - previous_invoke_intf_count);

  post_dexen_changes(scope, stores);
}
Example #2
0
void AccessMarkingPass::run_pass(DexStoresVector& stores,
                                 ConfigFiles& /* conf */,
                                 PassManager& pm) {
  auto scope = build_class_scope(stores);
  ClassHierarchy ch = build_type_hierarchy(scope);
  SignatureMap sm = build_signature_map(ch);
  if (m_finalize_classes) {
    auto n_classes_final = mark_classes_final(scope, ch);
    pm.incr_metric("finalized_classes", n_classes_final);
    TRACE(ACCESS, 1, "Finalized %lu classes\n", n_classes_final);
  }
  if (m_finalize_methods) {
    auto n_methods_final = mark_methods_final(scope, ch);
    pm.incr_metric("finalized_methods", n_methods_final);
    TRACE(ACCESS, 1, "Finalized %lu methods\n", n_methods_final);
  }
  if (m_finalize_fields) {
    auto n_fields_final = mark_fields_final(scope);
    pm.incr_metric("finalized_fields", n_fields_final);
    TRACE(ACCESS, 1, "Finalized %lu fields\n", n_fields_final);
  }
  auto candidates = devirtualize(sm);
  auto dmethods = direct_methods(scope);
  candidates.insert(candidates.end(), dmethods.begin(), dmethods.end());
  if (m_privatize_methods) {
    auto privates = find_private_methods(scope, candidates);
    fix_call_sites_private(scope, privates);
    mark_methods_private(privates);
    pm.incr_metric("privatized_methods", privates.size());
    TRACE(ACCESS, 1, "Privatized %lu methods\n", privates.size());
  }
}
Example #3
0
bool should_rename(DexClass *clazz,
    std::vector<std::string>& pre_patterns,
    std::vector<std::string>& post_patterns,
    std::unordered_set<const DexType*>& untouchables,
    bool rename_annotations,
    PassManager& mgr) {
  if (!rename_annotations && is_annotation(clazz)) {
    mgr.incr_metric(METRIC_CANT_RENAME_ANNOTATION, 1);
    return false;
  }
  if (untouchables.count(clazz->get_type())) {
    mgr.incr_metric(METRIC_CANT_RENAME_UNTOUCHABLE, 1);
    return false;
  }
  auto chstring = clazz->get_type()->get_name()->c_str();
  /* We're assuming anonymous classes are safe always safe to rename. */
  auto last_cash = strrchr(chstring, '$');
  if (last_cash != nullptr) {
    auto val = *++last_cash;
    if (val >= '0' && val <= '9') {
      match_inner++;
      return true;
    }
  }
  /* Check for more aggressive, but finer grained filters first */
  for (auto p : pre_patterns) {
    auto substr = strstr(chstring, p.c_str());
    if (substr != nullptr) {
      if (p.length() > 1) {
        match_long++;
      } else {
        match_short++;
      }
      return true;
    }
  }
  if (!can_rename(clazz) && !can_delete(clazz)) {
    mgr.incr_metric(METRIC_CANT_RENAME_AND_CANT_DELETE, 1);
    return false;
  }
  /* Check for wider, less precise filters */
  for (auto p : post_patterns) {
    auto substr = strstr(chstring, p.c_str());
    if (substr != nullptr) {
      if (p.length() > 1) {
        match_long++;
      } else {
        match_short++;
      }
      return true;
    }
  }
  mgr.incr_metric(METRIC_NOT_WHITELISTED, 1);
  return false;
}
Example #4
0
void PassImpl::run_pass(DexStoresVector& stores,
                        ConfigFiles& config,
                        PassManager& mgr) {
  if (m_config.create_runtime_asserts) {
    m_config.runtime_assert =
        RuntimeAssertTransform::Config(config.get_proguard_map());
  }

  auto scope = build_class_scope(stores);
  run(scope);
  mgr.incr_metric("branches_removed", m_transform_stats.branches_removed);
  mgr.incr_metric("materialized_consts", m_transform_stats.materialized_consts);
  mgr.incr_metric("constant_fields", m_stats.constant_fields);
  mgr.incr_metric("constant_methods", m_stats.constant_methods);
}
Example #5
0
void RemoveInterfacePass::run_pass(DexStoresVector& stores,
                                   ConfigFiles&,
                                   PassManager& mgr) {
  auto scope = build_class_scope(stores);
  TypeSystem type_system(scope);
  for (const auto root : m_interface_roots) {
    remove_interfaces_for_root(scope, stores, root, type_system);
  }
  mgr.incr_metric("num_total_interface", m_total_num_interface);
  mgr.incr_metric("num_interface_excluded", m_num_interface_excluded);
  mgr.incr_metric("num_interface_removed", m_num_interface_removed);
  TRACE(RM_INTF, 5, "total number of interfaces %ld\n", m_total_num_interface);
  TRACE(RM_INTF, 5, "number of excluded interfaces %ld\n",
        m_num_interface_excluded);
  TRACE(RM_INTF, 5, "number of removed interfaces %ld\n",
        m_num_interface_removed);

  for (const auto& stat : m_dispatch_stats) {
    std::stringstream metric;
    metric << "num_dispatch_" << stat.first;
    mgr.incr_metric(metric.str(), stat.second);
  }
}
Example #6
0
void RemoveUnreachablePass::run_pass(DexStoresVector& stores,
                                     ConfigFiles& conf,
                                     PassManager& pm) {
  // Store names of removed classes and methods
  ConcurrentSet<std::string> removed_symbols;

  if (pm.no_proguard_rules()) {
    TRACE(RMU,
          1,
          "RemoveUnreachablePass not run because no "
          "ProGuard configuration was provided.");
    return;
  }
  bool output_unreachable_symbols = pm.get_current_pass_info()->repeat == 0 &&
                                    !m_unreachable_symbols_file_name.empty();
  int num_ignore_check_strings = 0;
  auto reachables = reachability::compute_reachable_objects(
      stores, m_ignore_sets, &num_ignore_check_strings);
  reachability::ObjectCounts before = reachability::count_objects(stores);
  TRACE(RMU, 1, "before: %lu classes, %lu fields, %lu methods\n",
        before.num_classes, before.num_fields, before.num_methods);
  reachability::sweep(stores, *reachables,
                      output_unreachable_symbols ? &removed_symbols : nullptr);
  reachability::ObjectCounts after = reachability::count_objects(stores);
  TRACE(RMU, 1, "after: %lu classes, %lu fields, %lu methods\n",
        after.num_classes, after.num_fields, after.num_methods);
  pm.incr_metric("num_ignore_check_strings", num_ignore_check_strings);
  pm.incr_metric("classes_removed", before.num_classes - after.num_classes);
  pm.incr_metric("fields_removed", before.num_fields - after.num_fields);
  pm.incr_metric("methods_removed", before.num_methods - after.num_methods);

  if (output_unreachable_symbols) {
    std::string filepath = conf.metafile(m_unreachable_symbols_file_name);
    write_out_removed_symbols(filepath, removed_symbols);
  }
}
Example #7
0
void RenameClassesPass::run_pass(DexStoresVector& stores,
                                 ConfigFiles& cfg,
                                 PassManager& mgr) {
  const JsonWrapper& json_cfg = cfg.get_json_config();
  if (json_cfg.get("emit_name_based_locators", false)) {
    // TODO: Purge the old RenameClassesPass entirely everywhere.
    fprintf(stderr,
            "[RenameClassesPass] error: Configuration option "
            "emit_locator_strings is not compatible with RenameClassesPass. "
            "Upgrade to RenameClassesPassV2 instead.\n");
    exit(EXIT_FAILURE);
  }

  auto scope = build_class_scope(stores);
  ClassHierarchy ch = build_type_hierarchy(scope);
  std::unordered_set<const DexType*> untouchables;
  for (const auto& base : m_untouchable_hierarchies) {
    auto base_type = DexType::get_type(base.c_str());
    if (base_type != nullptr) {
      untouchables.insert(base_type);
      TypeSet children;
      get_all_children(ch, base_type, children);
      untouchables.insert(children.begin(), children.end());
    }
  }
  mgr.incr_metric(METRIC_CLASSES_IN_SCOPE, scope.size());
  rename_classes(
      scope, m_pre_filter_whitelist, m_post_filter_whitelist,
      untouchables, m_rename_annotations, mgr);
  TRACE(RENAME, 1,
      "renamed classes: %d anon classes, %d from single char patterns, "
      "%d from multi char patterns\n",
      match_inner,
      match_short,
      match_long);
  TRACE(RENAME, 1, "String savings, at least %d bytes \n",
      base_strings_size - ren_strings_size);
}
Example #8
0
void RegAllocPass::run_pass(DexStoresVector& stores,
                            ConfigFiles&,
                            PassManager& mgr) {
  using Output = graph_coloring::Allocator::Stats;
  auto scope = build_class_scope(stores);
  auto stats = walk::parallel::reduce_methods<Output>(
      scope,
      [this](DexMethod* m) { // mapper
        graph_coloring::Allocator::Stats stats;
        if (m->get_code() == nullptr) {
          return stats;
        }
        auto& code = *m->get_code();

        TRACE(REG, 3, "Handling %s:\n", SHOW(m));
        TRACE(REG,
              5,
              "regs:%d code:\n%s\n",
              code.get_registers_size(),
              SHOW(&code));
        try {
          live_range::renumber_registers(&code, /* width_aware */ true);
          // The transformations below all require a CFG. Build it once
          // here instead of requiring each transform to build it.
          code.build_cfg(/* editable */ false);
          graph_coloring::Allocator allocator(m_allocator_config);
          allocator.allocate(m);
          stats.accumulate(allocator.get_stats());

          TRACE(REG,
                5,
                "After alloc: regs:%d code:\n%s\n",
                code.get_registers_size(),
                SHOW(&code));
        } catch (std::exception&) {
          fprintf(stderr, "Failed to allocate %s\n", SHOW(m));
          fprintf(stderr, "%s\n", SHOW(code.cfg()));
          throw;
        }
        return stats;
      },
      [](Output a, Output b) { // reducer
        a.accumulate(b);
        return a;
      });

  TRACE(REG, 1, "Total reiteration count: %lu\n", stats.reiteration_count);
  TRACE(REG, 1, "Total Params spilled early: %lu\n", stats.params_spill_early);
  TRACE(REG, 1, "Total spill count: %lu\n", stats.moves_inserted());
  TRACE(REG, 1, "  Total param spills: %lu\n", stats.param_spill_moves);
  TRACE(REG, 1, "  Total range spills: %lu\n", stats.range_spill_moves);
  TRACE(REG, 1, "  Total global spills: %lu\n", stats.global_spill_moves);
  TRACE(REG, 1, "  Total splits: %lu\n", stats.split_moves);
  TRACE(REG, 1, "Total coalesce count: %lu\n", stats.moves_coalesced);
  TRACE(REG, 1, "Total net moves: %ld\n", stats.net_moves());

  mgr.incr_metric("param spilled too early", stats.params_spill_early);
  mgr.incr_metric("reiteration_count", stats.reiteration_count);
  mgr.incr_metric("spill_count", stats.moves_inserted());
  mgr.incr_metric("coalesce_count", stats.moves_coalesced);
  mgr.incr_metric("net_moves", stats.net_moves());

  mgr.record_running_regalloc();
}
Example #9
0
void rename_classes(
    Scope& scope,
    std::vector<std::string>& pre_whitelist_patterns,
    std::vector<std::string>& post_whitelist_patterns,
    std::unordered_set<const DexType*>& untouchables,
    bool rename_annotations,
    PassManager& mgr) {
  unpackage_private(scope);
  int clazz_ident = 0;
  std::map<DexString*, DexString*, dexstrings_comparator> aliases;
  for(auto clazz: scope) {
    if (!should_rename(
        clazz, pre_whitelist_patterns, post_whitelist_patterns,
        untouchables, rename_annotations, mgr)) {
      continue;
    }
    char clzname[4];
    get_next_ident(clzname, clazz_ident);

    auto dtype = clazz->get_type();
    auto oldname = dtype->get_name();


    // The X helps our hacked Dalvik classloader recognize that a
    // class name is the output of the redex renamer and thus will
    // never be found in the Android platform.
    // The $ indicates that the class was originally an inner class.
    // Some code, most notably android instrumentation runner, uses
    // this information to decide whether or not to classload the class.
    bool inner = strrchr(oldname->c_str(), '$');
    char descriptor[10];
    sprintf(descriptor, "LX%s%s;", inner ? "$" : "", clzname);
    auto dstring = DexString::make_string(descriptor);
    aliases[oldname] = dstring;
    dtype->set_name(dstring);
    std::string old_str(oldname->c_str());
    std::string new_str(descriptor);
    base_strings_size += strlen(oldname->c_str());
    base_strings_size += strlen(dstring->c_str());
    TRACE(RENAME, 4, "'%s'->'%s'\n", oldname->c_str(), descriptor);
    while (1) {
     std::string arrayop("[");
      arrayop += oldname->c_str();
      oldname = DexString::get_string(arrayop.c_str());
      if (oldname == nullptr) {
        break;
      }
      auto arraytype = DexType::get_type(oldname);
      if (arraytype == nullptr) {
        break;
      }
      std::string newarraytype("[");
      newarraytype += dstring->c_str();
      dstring = DexString::make_string(newarraytype.c_str());
      aliases[oldname] = dstring;
      arraytype->set_name(dstring);
    }
  }
  mgr.incr_metric(METRIC_RENAMED_CLASSES, match_short + match_long + match_inner);
  /* Now we need to re-write the Signature annotations.  They use
   * Strings rather than Type's, so they have to be explicitly
   * handled.
   */

  /* Generics of the form Type<> turn into the Type string
   * sans the ';'.  So, we have to alias those if they
   * exist.  Signature annotations suck.
   */
  for (auto apair : aliases) {
    char buf[MAX_DESCRIPTOR_LENGTH];
    const char *sourcestr = apair.first->c_str();
    size_t sourcelen = strlen(sourcestr);
    if (sourcestr[sourcelen - 1] != ';') continue;
    strcpy(buf, sourcestr);
    buf[sourcelen - 1] = '\0';
    auto dstring = DexString::get_string(buf);
    if (dstring == nullptr) continue;
    strcpy(buf, apair.second->c_str());
    buf[strlen(apair.second->c_str()) - 1] = '\0';
    auto target = DexString::make_string(buf);
    aliases[dstring] = target;
  }
  walk::annotations(scope, [&](DexAnnotation* anno) {
    static DexType *dalviksig =
      DexType::get_type("Ldalvik/annotation/Signature;");
    if (anno->type() != dalviksig) return;
    auto elems = anno->anno_elems();
    for (auto elem : elems) {
      auto ev = elem.encoded_value;
      if (ev->evtype() != DEVT_ARRAY) continue;
      auto arrayev = static_cast<DexEncodedValueArray*>(ev);
      auto const& evs = arrayev->evalues();
      for (auto strev : *evs) {
        if (strev->evtype() != DEVT_STRING) continue;
        auto stringev = static_cast<DexEncodedValueString*>(strev);
        if (aliases.count(stringev->string())) {
          TRACE(RENAME, 5, "Rewriting Signature from '%s' to '%s'\n",
              stringev->string()->c_str(),
              aliases[stringev->string()]->c_str());
          stringev->string(aliases[stringev->string()]);
        }
      }
    }
  });
}
Example #10
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);
}
Example #11
0
static void strip_src_strings(
  DexStoresVector& stores, const char* map_path, PassManager& mgr) {
  size_t shortened = 0;
  size_t string_savings = 0;
  std::unordered_map<DexString*, std::vector<DexString*>> global_src_strings;
  std::unordered_set<DexString*> shortened_used;
  for (auto& classes : DexStoreClassesIterator(stores)) {
    for (auto const& clazz : classes) {
      auto src_string = clazz->get_source_file();
      if (src_string) {
        // inserting actual source files into this set will cause them to not
        // get used --- as the whole point of this analysis is to substitute
        // source file strings
        shortened_used.insert(src_string);
      }
    }
  }

  for (auto& classes : DexStoreClassesIterator(stores)) {
    std::unordered_map<DexString*, DexString*> src_to_shortened;
    std::vector<DexString*> current_dex_strings;
    for (auto const& clazz : classes) {
      clazz->gather_strings(current_dex_strings);
    }
    sort_unique(current_dex_strings, compare_dexstrings);
    // reverse current_dex_strings vector, so that we prefer strings that will
    // get smaller indices
    std::reverse(std::begin(current_dex_strings),
                 std::end(current_dex_strings));

    for (auto const& clazz : classes) {
      auto src_string = clazz->get_source_file();
      if (!src_string) {
        continue;
      }
      DexString* shortened_src_string = nullptr;
      if (src_to_shortened.count(src_string) == 0) {
        shortened_src_string =
            get_suitable_string(shortened_used, current_dex_strings);
        if (!shortened_src_string) {
          opt_warn(UNSHORTENED_SRC_STRING, "%s\n", SHOW(src_string));
          shortened_src_string = src_string;
        } else {
          shortened++;
          string_savings += strlen(src_string->c_str());
        }
        src_to_shortened[src_string] = shortened_src_string;
        shortened_used.emplace(shortened_src_string);
        global_src_strings[src_string].push_back(shortened_src_string);
      } else {
        shortened_src_string = src_to_shortened[src_string];
      }
      clazz->set_source_file(shortened_src_string);
    }
  }

  TRACE(SHORTEN, 1, "src strings shortened %ld, %lu bytes saved\n", shortened,
      string_savings);

  mgr.incr_metric(METRIC_SHORTENED_STRINGS, shortened);
  mgr.incr_metric(METRIC_BYTES_SAVED, string_savings);

  // generate mapping
  FILE* fd = fopen(map_path, "w");
  if (fd == nullptr) {
    perror("Error writing mapping file");
    return;
  }

  for (auto it : global_src_strings) {
    auto desc_vector = it.second;
    sort_unique(desc_vector);
    fprintf(fd, "%s ->", it.first->c_str());
    for (auto str : desc_vector) {
      fprintf(fd, " %s,", str->c_str());
    }
    fprintf(fd, "\n");
  }
  fclose(fd);
}