libredex/ConfigFiles.cpp (299 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 "ConfigFiles.h"
#include <boost/filesystem.hpp>
#include <fstream>
#include <json/json.h>
#include <string>
#include <vector>
#include "Debug.h"
#include "DexClass.h"
#include "FrameworkApi.h"
#include "InlinerConfig.h"
#include "MethodProfiles.h"
#include "ProguardMap.h"
ConfigFiles::ConfigFiles(const Json::Value& config, const std::string& outdir)
: m_json(config),
outdir(outdir),
m_global_config(GlobalConfig::default_registry()),
m_proguard_map(
new ProguardMap(config.get("proguard_map", "").asString(),
config.get("use_new_rename_map", 0).asBool())),
m_printseeds(config.get("printseeds", "").asString()),
m_method_profiles(new method_profiles::MethodProfiles()) {
m_coldstart_class_filename = config.get("coldstart_classes", "").asString();
if (m_coldstart_class_filename.empty()) {
m_coldstart_class_filename =
config.get("default_coldstart_classes", "").asString();
}
uint32_t instruction_size_bitwidth_limit =
config.get("instruction_size_bitwidth_limit", 0).asUInt();
always_assert_log(
instruction_size_bitwidth_limit < 32,
"instruction_size_bitwidth_limit must be between 0 and 31, actual: %u\n",
instruction_size_bitwidth_limit);
m_instruction_size_bitwidth_limit = instruction_size_bitwidth_limit;
}
ConfigFiles::ConfigFiles(const Json::Value& config) : ConfigFiles(config, "") {}
ConfigFiles::~ConfigFiles() {
// Here so that we can use `unique_ptr` to hide full class defs in the header.
}
/**
* This function relies on the g_redex.
*/
const std::unordered_set<DexType*>& ConfigFiles::get_no_optimizations_annos() {
if (m_no_optimizations_annos.empty()) {
Json::Value no_optimizations_anno;
m_json.get("no_optimizations_annotations", Json::nullValue,
no_optimizations_anno);
if (!no_optimizations_anno.empty()) {
for (auto const& config_anno_name : no_optimizations_anno) {
std::string anno_name = config_anno_name.asString();
DexType* anno = DexType::get_type(anno_name.c_str());
if (anno) m_no_optimizations_annos.insert(anno);
}
}
}
return m_no_optimizations_annos;
}
/**
* This function relies on the g_redex.
*/
const std::unordered_set<DexMethodRef*>& ConfigFiles::get_pure_methods() {
if (m_pure_methods.empty()) {
Json::Value pure_methods;
m_json.get("pure_methods", Json::nullValue, pure_methods);
if (!pure_methods.empty()) {
for (auto const& method_name : pure_methods) {
std::string name = method_name.asString();
DexMethodRef* method = DexMethod::get_method(name);
if (method) m_pure_methods.insert(method);
}
}
}
return m_pure_methods;
}
/**
* This function relies on the g_redex.
*/
const std::unordered_set<const DexString*>&
ConfigFiles::get_finalish_field_names() {
if (m_finalish_field_names.empty()) {
Json::Value finalish_field_names;
m_json.get("finalish_field_names", Json::nullValue, finalish_field_names);
if (!finalish_field_names.empty()) {
for (auto const& field_name : finalish_field_names) {
std::string name = field_name.asString();
auto* str = DexString::make_string(name);
if (str) m_finalish_field_names.insert(str);
}
}
}
return m_finalish_field_names;
}
/**
* This function relies on the g_redex.
*/
const std::unordered_set<DexType*>& ConfigFiles::get_do_not_devirt_anon() {
if (m_no_devirtualize_annos.empty()) {
std::vector<std::string> no_devirtualize_anno_names;
m_json.get("no_devirtualize_annos", {}, no_devirtualize_anno_names);
if (!no_devirtualize_anno_names.empty()) {
for (auto& name : no_devirtualize_anno_names) {
auto* typ = DexType::get_type(name);
if (typ) {
m_no_devirtualize_annos.insert(typ);
}
}
}
}
return m_no_devirtualize_annos;
}
/**
* Read an interdex list file and return as a vector of appropriately-formatted
* classname strings.
*/
std::vector<std::string> ConfigFiles::load_coldstart_classes() {
if (m_coldstart_class_filename.empty()) {
return {};
}
const char* kClassTail = ".class";
const size_t lentail = strlen(kClassTail);
auto file = m_coldstart_class_filename.c_str();
std::vector<std::string> coldstart_classes;
std::ifstream input(file);
if (!input) {
fprintf(stderr,
"[error] Can not open <coldstart_classes> file, path is %s\n",
file);
exit(EXIT_FAILURE);
}
std::string clzname;
while (input >> clzname) {
long position = clzname.length() - lentail;
always_assert_log(position >= 0,
"Bailing, invalid class spec '%s' in interdex file %s\n",
clzname.c_str(), file);
clzname.replace(position, lentail, ";");
coldstart_classes.emplace_back(
m_proguard_map->translate_class("L" + clzname));
}
return coldstart_classes;
}
/**
* Read a map of {list_name : class_list} from json
*/
std::unordered_map<std::string, std::vector<std::string>>
ConfigFiles::load_class_lists() {
std::unordered_map<std::string, std::vector<std::string>> lists;
std::string class_lists_filename;
this->m_json.get("class_lists", "", class_lists_filename);
if (class_lists_filename.empty()) {
return lists;
}
std::ifstream input(class_lists_filename);
Json::Reader reader;
Json::Value root;
bool parsing_succeeded = reader.parse(input, root);
always_assert_log(
parsing_succeeded, "Failed to parse class list json from file: %s\n%s",
class_lists_filename.c_str(), reader.getFormattedErrorMessages().c_str());
for (Json::ValueIterator it = root.begin(); it != root.end(); ++it) {
std::vector<std::string> class_list;
Json::Value current_list = *it;
for (Json::ValueIterator list_it = current_list.begin();
list_it != current_list.end();
++list_it) {
lists[it.key().asString()].push_back((*list_it).asString());
}
}
lists["secondary_dex_head.list"] = get_coldstart_classes();
return lists;
}
const std::vector<std::string>& ConfigFiles::get_dead_class_list() {
if (!m_dead_class_list_attempted) {
m_dead_class_list_attempted = true;
std::string dead_class_list_filename;
this->m_json.get("dead_class_list", "", dead_class_list_filename);
if (!dead_class_list_filename.empty()) {
std::ifstream input(dead_class_list_filename);
if (!input) {
fprintf(stderr,
"[error] Can not open <dead_class_list> file, path is %s\n",
dead_class_list_filename.c_str());
exit(EXIT_FAILURE);
}
std::string classname;
while (input >> classname) {
std::string converted = std::string("L") + classname + std::string(";");
std::replace(converted.begin(), converted.end(), '.', '/');
auto translated = m_proguard_map->translate_class(converted);
m_dead_class_list.push_back(std::move(translated));
}
}
}
return m_dead_class_list;
}
void ConfigFiles::ensure_agg_method_stats_loaded() {
std::vector<std::string> csv_filenames;
get_json_config().get("agg_method_stats_files", {}, csv_filenames);
if (csv_filenames.empty() || m_method_profiles->is_initialized()) {
return;
}
m_method_profiles->initialize(csv_filenames);
}
void ConfigFiles::load_inliner_config(inliner::InlinerConfig* inliner_config) {
Json::Value config;
m_json.get("inliner", Json::nullValue, config);
if (config.empty()) {
m_json.get("MethodInlinePass", Json::nullValue, config);
always_assert_log(
config.empty(),
"MethodInlinePass is no longer used for inliner config, use "
"\"inliner\"");
fprintf(stderr, "WARNING: No inliner config\n");
return;
}
JsonWrapper jw(config);
jw.get("delete_non_virtuals", true, inliner_config->delete_non_virtuals);
jw.get("virtual", true, inliner_config->virtual_inline);
jw.get("true_virtual_inline", false, inliner_config->true_virtual_inline);
jw.get("throws", false, inliner_config->throws_inline);
jw.get("throw_after_no_return", false, inliner_config->throw_after_no_return);
jw.get("enforce_method_size_limit",
true,
inliner_config->enforce_method_size_limit);
jw.get("use_call_site_summaries", true,
inliner_config->use_call_site_summaries);
jw.get("intermediate_shrinking", false,
inliner_config->intermediate_shrinking);
jw.get("multiple_callers", false, inliner_config->multiple_callers);
auto& shrinker_config = inliner_config->shrinker;
jw.get("run_const_prop", false, shrinker_config.run_const_prop);
jw.get("run_cse", false, shrinker_config.run_cse);
jw.get("run_copy_prop", false, shrinker_config.run_copy_prop);
jw.get("run_local_dce", false, shrinker_config.run_local_dce);
jw.get("run_reg_alloc", false, shrinker_config.run_reg_alloc);
jw.get("run_fast_reg_alloc", false, shrinker_config.run_fast_reg_alloc);
jw.get("run_dedup_blocks", false, shrinker_config.run_dedup_blocks);
jw.get("debug", false, inliner_config->debug);
jw.get("blocklist", {}, inliner_config->m_blocklist);
jw.get("caller_blocklist", {}, inliner_config->m_caller_blocklist);
jw.get("intradex_allowlist", {}, inliner_config->m_intradex_allowlist);
jw.get("reg_alloc_random_forest", "",
shrinker_config.reg_alloc_random_forest);
jw.get("respect_sketchy_methods", true,
inliner_config->respect_sketchy_methods);
jw.get("check_min_sdk_refs", true, inliner_config->check_min_sdk_refs);
std::vector<std::string> no_inline_annos;
jw.get("no_inline_annos", {}, no_inline_annos);
for (const auto& type_s : no_inline_annos) {
auto type = DexType::get_type(type_s.c_str());
if (type != nullptr) {
inliner_config->m_no_inline_annos.emplace(type);
} else {
fprintf(stderr, "WARNING: Cannot find no_inline annotation %s\n",
type_s.c_str());
}
}
std::vector<std::string> force_inline_annos;
jw.get("force_inline_annos", {}, force_inline_annos);
for (const auto& type_s : force_inline_annos) {
auto type = DexType::get_type(type_s.c_str());
if (type != nullptr) {
inliner_config->m_force_inline_annos.emplace(type);
} else {
fprintf(stderr, "WARNING: Cannot find force_inline annotation %s\n",
type_s.c_str());
}
}
}
const inliner::InlinerConfig& ConfigFiles::get_inliner_config() {
if (m_inliner_config == nullptr) {
m_inliner_config = std::make_unique<inliner::InlinerConfig>();
load_inliner_config(m_inliner_config.get());
}
return *m_inliner_config;
}
void ConfigFiles::parse_global_config() {
m_global_config.parse_config(m_json);
}
void ConfigFiles::load(const Scope& scope) {
get_inliner_config();
m_inliner_config->populate(scope);
}
void ConfigFiles::process_unresolved_method_profile_lines() {
ensure_agg_method_stats_loaded();
m_method_profiles->process_unresolved_lines();
}
const api::AndroidSDK& ConfigFiles::get_android_sdk_api(int32_t min_sdk_api) {
if (m_android_min_sdk_api == nullptr) {
always_assert(m_min_sdk_api_level == 0); // not set
m_min_sdk_api_level = min_sdk_api;
auto api_file = get_android_sdk_api_file(min_sdk_api);
m_android_min_sdk_api = std::make_unique<api::AndroidSDK>(api_file);
}
always_assert(min_sdk_api == m_min_sdk_api_level);
return *m_android_min_sdk_api;
}
const ProguardMap& ConfigFiles::get_proguard_map() const {
return *m_proguard_map;
}
bool ConfigFiles::force_single_dex() const {
return m_json.get("force_single_dex", false);
}
bool ConfigFiles::emit_incoming_hashes() const {
return m_json.get("emit_incoming_hashes", false);
}
bool ConfigFiles::emit_outgoing_hashes() const {
return m_json.get("emit_outgoing_hashes", false);
}
bool ConfigFiles::create_init_class_insns() const {
return m_json.get("create_init_class_insns", true);
}
bool ConfigFiles::finalize_resource_table() const {
return m_json.get("finalize_resource_table", false);
}
void ConfigFiles::set_outdir(const std::string& new_outdir) {
// Gotta ensure "meta" exists.
auto meta_path = boost::filesystem::path(new_outdir) / "meta";
boost::filesystem::create_directory(meta_path);
outdir = new_outdir;
}