/* * Check that analyze_enum_clinit returns the correct enum field -> ordinal * mapping. */ TEST_F(RedexTest, OrdinalAnalysis) { always_assert(load_class_file(std::getenv("enum_class_file"))); auto dexfile = std::getenv("dexfile"); std::vector<DexStore> stores; DexMetadata dm; dm.set_id("classes"); DexStore root_store(dm); root_store.add_classes(load_classes_from_dex(dexfile)); auto scope = build_class_scope(root_store.get_dexen()); auto enumA = type_class(DexType::get_type(ENUM_A)); auto enum_field_to_ordinal = optimize_enums::analyze_enum_clinit(enumA); auto enumA_zero = static_cast<DexField*>( DexField::get_field("Lcom/facebook/redextest/EnumA;.TYPE_A_0:Lcom/" "facebook/redextest/EnumA;")); auto enumA_one = static_cast<DexField*>( DexField::get_field("Lcom/facebook/redextest/EnumA;.TYPE_A_1:Lcom/" "facebook/redextest/EnumA;")); auto enumA_two = static_cast<DexField*>( DexField::get_field("Lcom/facebook/redextest/EnumA;.TYPE_A_2:Lcom/" "facebook/redextest/EnumA;")); EXPECT_EQ(enum_field_to_ordinal.at(enumA_zero), 0); EXPECT_EQ(enum_field_to_ordinal.at(enumA_one), 1); EXPECT_EQ(enum_field_to_ordinal.at(enumA_two), 2); }
TEST(PropagationTest1, localDCE1) { g_redex = new RedexContext(); const char* dexfile = std::getenv("dexfile"); ASSERT_NE(nullptr, dexfile); std::vector<DexStore> stores; DexMetadata dm; dm.set_id("classes"); DexStore root_store(dm); root_store.add_classes(load_classes_from_dex(dexfile)); DexClasses& classes = root_store.get_dexen().back(); stores.emplace_back(std::move(root_store)); std::cout << "Loaded classes: " << classes.size() << std::endl ; TRACE(DCE, 2, "Code before:\n"); for(const auto& cls : classes) { TRACE(DCE, 2, "Class %s\n", SHOW(cls)); for (const auto& dm : cls->get_dmethods()) { TRACE(DCE, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagate") == 0) { TRACE(DCE, 2, "dmethod: %s\n", SHOW(dm->get_code())); } } } std::vector<Pass*> passes = { new PeepholePass(), new LocalDcePass(), }; PassManager manager(passes); manager.set_testing_mode(); Json::Value conf_obj = Json::nullValue; ConfigFiles dummy_cfg(conf_obj); manager.run_passes(stores, dummy_cfg); TRACE(DCE, 2, "Code after:\n"); for(const auto& cls : classes) { TRACE(DCE, 2, "Class %s\n", SHOW(cls)); for (const auto& dm : cls->get_dmethods()) { TRACE(DCE, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagate") == 0) { TRACE(DCE, 2, "dmethod: %s\n", SHOW(dm->get_code())); for (auto& mie : InstructionIterable(dm->get_code())) { auto instruction = mie.insn; // Make sure there is no invoke-virtual in the optimized method. ASSERT_NE(instruction->opcode(), OPCODE_INVOKE_VIRTUAL); // Make sure there is no const-class in the optimized method. ASSERT_NE(instruction->opcode(), OPCODE_CONST_CLASS); } } } } }
TEST(DedupBlocksTest, useSwitch) { g_redex = new RedexContext(); const char* dexfile = std::getenv("dexfile"); EXPECT_NE(nullptr, dexfile); std::vector<DexStore> stores; DexMetadata dm; dm.set_id("classes"); DexStore root_store(dm); root_store.add_classes(load_classes_from_dex(dexfile)); DexClasses& classes = root_store.get_dexen().back(); stores.emplace_back(std::move(root_store)); TRACE(RME, 1, "Code before:\n"); for (const auto& cls : classes) { TRACE(RME, 1, "Class %s\n", SHOW(cls)); for (const auto& m : cls->get_vmethods()) { TRACE(RME, 1, "\nmethod %s:\n", SHOW(m)); IRCode* code = m->get_code(); code->build_cfg(true); EXPECT_EQ(2, count_sgets(code->cfg())); code->clear_cfg(); } } auto copy_prop = new CopyPropagationPass(); copy_prop->m_config.static_finals = true; copy_prop->m_config.wide_registers = true; std::vector<Pass*> passes = { copy_prop }; PassManager manager(passes); manager.set_testing_mode(); Json::Value conf_obj = Json::nullValue; Scope external_classes; ConfigFiles dummy_cfg(conf_obj); manager.run_passes(stores, external_classes, dummy_cfg); TRACE(RME, 1, "Code after:\n"); for (const auto& cls : classes) { for (const auto& m : cls->get_vmethods()) { TRACE(RME, 1, "\nmethod %s:\n", SHOW(m)); IRCode* code = m->get_code(); code->build_cfg(true); if (strcmp(m->get_name()->c_str(), "remove") == 0) { EXPECT_EQ(1, count_sgets(code->cfg())); } else { EXPECT_EQ(2, count_sgets(code->cfg())); } code->clear_cfg(); } } }
int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: ./TestGenerator classes.dex" << std::endl; } auto dex = argv[1]; g_redex = new RedexContext(); auto classes = load_classes_from_dex(dex); auto runner_cls = std::find_if(classes.begin(), classes.end(), [](const DexClass* cls) { return cls->get_name() == DexString::get_string( "Lcom/facebook/redex/equivalence/EquivalenceMain;"); }); redex_assert(runner_cls != classes.end()); EquivalenceTest::generate_all(*runner_cls); Json::Value json(Json::objectValue); ConfigFiles conf(json); std::unique_ptr<PositionMapper> pos_mapper(PositionMapper::make("", "")); DexStore store("classes"); store.set_dex_magic(load_dex_magic_from_dex(dex)); store.add_classes(classes); DexStoresVector stores; stores.emplace_back(std::move(store)); instruction_lowering::run(stores); write_classes_to_dex(dex, &classes, nullptr /* LocatorIndex* */, false /* name-based locators */, 0, 0, conf, pos_mapper.get(), nullptr, nullptr, nullptr /* IODIMetadata* */, stores[0].get_dex_magic()); delete g_redex; return 0; }
TEST(ResultPropagationTest, useSwitch) { g_redex = new RedexContext(); const char* dexfile = std::getenv("dexfile"); EXPECT_NE(nullptr, dexfile); std::vector<DexStore> stores; DexMetadata dm; dm.set_id("classes"); DexStore root_store(dm); root_store.add_classes(load_classes_from_dex(dexfile)); DexClasses& classes = root_store.get_dexen().back(); stores.emplace_back(std::move(root_store)); std::vector<Pass*> passes = { new ResultPropagationPass(), }; PassManager manager(passes); manager.set_testing_mode(); Json::Value conf_obj = Json::nullValue; ConfigFiles dummy_cfg(conf_obj); manager.run_passes(stores, dummy_cfg); int num_test_classes = 0; const char* test_class_prefix = "Lcom/facebook/redextest/ResultPropagation$"; const char* test_method_prefix = "returns_"; for (const auto& cls : classes) { if (strncmp(cls->get_name()->c_str(), test_class_prefix, strlen(test_class_prefix)) == 0) { TRACE(RP, 1, "test class %s\n", cls->get_name()->c_str()); num_test_classes++; int num_tests_in_class = 0; for (const auto& m : cls->get_vmethods()) { const auto method_name = m->get_name()->c_str(); TRACE(RP, 1, " test method %s\n", method_name); EXPECT_EQ(strncmp(method_name, test_method_prefix, strlen(test_method_prefix)), 0); const auto suffix = method_name + strlen(test_method_prefix); boost::optional<ParamIndex> expected; if (strcmp(suffix, "none") != 0) { expected = atoi(suffix); } IRCode* code = m->get_code(); code->build_cfg(/* editable */ true); auto actual = find_return_param_index(code->cfg()); code->clear_cfg(); if (expected) { EXPECT_TRUE(actual); EXPECT_EQ(*actual, *expected); } else { EXPECT_FALSE(actual); } num_tests_in_class++; } EXPECT_EQ(num_tests_in_class, 1); } } EXPECT_EQ(num_test_classes, 6); }
void load_root_dexen(DexStore& store, const std::string& dexen_dir_str, bool balloon, bool verbose, bool support_dex_v37) { namespace fs = boost::filesystem; fs::path dexen_dir_path(dexen_dir_str); redex_assert(fs::is_directory(dexen_dir_path)); // Discover dex files auto end = fs::directory_iterator(); std::vector<fs::path> dexen; for (fs::directory_iterator it(dexen_dir_path) ; it != end ; ++it) { auto file = it->path(); if (fs::is_regular_file(file) && !file.extension().compare(std::string(".dex"))) { dexen.emplace_back(file); } } /* * Comparator for dexen filename. 'classes.dex' should sort first, * followed by [^\d]*[\d]+.dex ordered by N numerically. */ auto dex_comparator = [](const fs::path& a, const fs::path& b){ boost::regex s_dex_regex("[^0-9]*([0-9]+)\\.dex"); auto as = a.filename().string(); auto bs = b.filename().string(); boost::smatch amatch; boost::smatch bmatch; bool amatched = boost::regex_match(as, amatch, s_dex_regex); bool bmatched = boost::regex_match(bs, bmatch, s_dex_regex); if (!amatched && bmatched) { return true; } else if (amatched && !bmatched) { return false; } else if (!amatched && !bmatched) { // Compare strings, probably the same return strcmp(as.c_str(), bs.c_str()) > 0; } else { // Compare captures as integers auto anum = std::stoi(amatch[1]); auto bnum = std::stoi(bmatch[1]); return bnum > anum ; } }; // Sort all discovered dex files std::sort(dexen.begin(), dexen.end(), dex_comparator); // Load all discovered dex files for (const auto& dex : dexen) { if (verbose) { TRACE(MAIN, 1, "Loading %s\n", dex.string().c_str()); } // N.B. throaway stats for now DexClasses classes = load_classes_from_dex(dex.string().c_str(), balloon, support_dex_v37); store.add_classes(std::move(classes)); } }
TEST(ConstantPropagationTest1, constantpropagation) { g_redex = new RedexContext(); const char* dexfile = "gen/native/redex/test/integ/constant-propagation-test-dex/constant-propagation.dex"; if (access(dexfile, R_OK) != 0) { dexfile = std::getenv("dexfile"); ASSERT_NE(nullptr, dexfile); } std::vector<DexClasses> dexen; dexen.emplace_back(load_classes_from_dex(dexfile)); DexClasses& classes = dexen.back(); std::cout << "Loaded classes: " << classes.size() << std::endl; TRACE(MAIN, 2, "Code before:\n"); for(const auto& cls : classes) { TRACE(MAIN, 2, "Class %s\n", SHOW(cls)); for (const auto& dm : cls->get_dmethods()) { TRACE(MAIN, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagation") == 0) { TRACE(MAIN, 2, "dmethod: %s\n", SHOW(dm->get_code())); } } } std::vector<Pass*> passes = { new LocalDcePass(), new DelInitPass(), new RemoveEmptyClassesPass(), // TODO: add constant propagation and conditional pruning optimization }; std::vector<KeepRule> null_rules; PassManager manager(passes, null_rules); Json::Value conf_obj = Json::nullValue; ConfigFiles dummy_cfg(conf_obj); manager.run_passes(dexen, dummy_cfg); TRACE(MAIN, 2, "Code after:\n"); for(const auto& cls : classes) { TRACE(MAIN, 2, "Class %s\n", SHOW(cls)); for (const auto& dm : cls->get_dmethods()) { TRACE(MAIN, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagation") == 0) { TRACE(MAIN, 2, "dmethod: %s\n", SHOW(dm->get_code())); for (auto const instruction : dm->get_code()->get_instructions()) { //The logic will be reverted when the future //development of constant propagation optimization is done, i.e., //The code will be changed to ASSERT_TRUE(false) // if IF_EQZ or New Class Instance instruction is found if (instruction->opcode() == OPCODE_IF_EQZ || instruction->opcode() == OPCODE_NEW_INSTANCE) { ASSERT_TRUE(true); } } } } } }
int main(int argc, char* argv[]) { signal(SIGSEGV, crash_backtrace); signal(SIGABRT, crash_backtrace); signal(SIGBUS, crash_backtrace); g_redex = new RedexContext(); auto passes = create_passes(); Arguments args; std::vector<KeepRule> rules; // Currently there are two sources that specify the library jars: // 1. The jar_path argument, which may specify one library jar. // 2. The library_jars vector, which lists the library jars specified in // the ProGuard configuration. // If -jarpath specified a library jar it is appended to the // library_jars vector so this vector can be used to iterate over // all the library jars regardless of whether they were specified // on the command line or ProGuard file. // TODO: Make the command line -jarpath option like a colon separated // list of library JARS. std::set<std::string> library_jars; auto start = parse_args(argc, argv, args); if (!dir_is_writable(args.out_dir)) { fprintf(stderr, "outdir %s is not a writable directory\n", args.out_dir.c_str()); exit(1); } if (!args.proguard_config.empty()) { if (!load_proguard_config_file( args.proguard_config.c_str(), &rules, &library_jars)) { fprintf(stderr, "ERROR: Unable to open proguard config %s\n", args.proguard_config.c_str()); // For now tolerate missing or unparseable ProGuard configuration files. // start = 0; } } else { TRACE(MAIN, 1, "Skipping parsing the proguard config file " "because no file was specified\n"); } for (const auto jar_path : args.jar_paths) { std::stringstream jar_stream(jar_path); std::string dependent_jar_path; while (std::getline(jar_stream, dependent_jar_path, ':')) { TRACE(MAIN, 2, "Dependent JAR specified on command-line: %s\n", dependent_jar_path.c_str()); library_jars.emplace(dependent_jar_path); } } if (start == 0 || start == argc) { usage(); exit(1); } DexClassesVector dexen; for (int i = start; i < argc; i++) { dexen.emplace_back(load_classes_from_dex(argv[i])); } for (const auto& library_jar : library_jars) { TRACE(MAIN, 1, "LIBRARY JAR: %s\n", library_jar.c_str()); if (!load_jar_file(library_jar.c_str())) { fprintf( stderr, "WARNING: Error in jar %s - continue. This may lead to unexpected " "behavior, please check your jars\n", library_jar.c_str()); } } ConfigFiles cfg(args.config); cfg.using_seeds = false; if (!args.seeds_filename.empty()) { cfg.using_seeds = init_seed_classes(args.seeds_filename) > 0; } PassManager manager(passes, rules, args.config); manager.run_passes(dexen, cfg); TRACE(MAIN, 1, "Writing out new DexClasses...\n"); LocatorIndex* locator_index = nullptr; if (args.config.get("emit_locator_strings", false).asBool()) { TRACE(LOC, 1, "Will emit class-locator strings for classloader optimization\n"); locator_index = new LocatorIndex(make_locator_index(dexen)); } dex_output_stats_t totals; std::vector<dex_output_stats_t> dexes_stats; auto pos_output = args.config.get("line_number_map", "").asString(); std::unique_ptr<PositionMapper> pos_mapper(PositionMapper::make(pos_output)); for (size_t i = 0; i < dexen.size(); i++) { std::stringstream ss; ss << args.out_dir + "/classes"; if (i > 0) { ss << (i + 1); } ss << ".dex"; auto stats = write_classes_to_dex( ss.str(), &dexen[i], locator_index, i, cfg, args.config, pos_mapper.get()); totals += stats; dexes_stats.push_back(stats); } auto stats_output = args.config.get("stats_output", "").asString(); auto method_move_map = args.config.get("method_move_map", "").asString(); pos_mapper->write_map(); output_stats(stats_output.c_str(), totals, dexes_stats, manager); output_moved_methods_map(method_move_map.c_str(), dexen, cfg); print_warning_summary(); delete g_redex; TRACE(MAIN, 1, "Done.\n"); return 0; }
TEST(ConstantPropagationTest1, constantpropagation) { g_redex = new RedexContext(); const char* dexfile = "gen/native/redex/test/integ/constant-propagation-test-dex/constant-propagation.dex"; if (access(dexfile, R_OK) != 0) { dexfile = std::getenv("dexfile"); ASSERT_NE(nullptr, dexfile); } std::vector<DexClasses> dexen; dexen.emplace_back(load_classes_from_dex(dexfile)); DexClasses& classes = dexen.back(); std::cout << "Loaded classes: " << classes.size() << std::endl; TRACE(CONSTP, 2, "Code before:\n"); for(const auto& cls : classes) { TRACE(CONSTP, 2, "Class %s\n", SHOW(cls)); if (filter_test_classes(cls->get_name()) < 2) { TRACE(CONSTP, 2, "Class %s\n", SHOW(cls)); for (const auto& dm : cls->get_dmethods()) { TRACE(CONSTP, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagation_1") == 0 || strcmp(dm->get_name()->c_str(), "propagation_2") == 0 || strcmp(dm->get_name()->c_str(), "propagation_3") == 0) { TRACE(CONSTP, 2, "dmethod: %s\n", SHOW(dm->get_code())); } } } } std::vector<Pass*> passes = { new ConstantPropagationPass(), new LocalDcePass() }; std::vector<KeepRule> null_rules; PassManager manager(passes, null_rules); Json::Value conf_obj = Json::nullValue; ConfigFiles dummy_cfg(conf_obj); dummy_cfg.using_seeds = true; manager.run_passes(dexen, dummy_cfg); TRACE(CONSTP, 2, "Code after:\n"); for(const auto& cls : classes) { TRACE(CONSTP, 2, "Class %s\n", SHOW(cls)); //ASSERT_NE(filter_test_classes(cls->get_name()), REMOVEDCLASS); if (filter_test_classes(cls->get_name()) == MAINCLASS) { for (const auto& dm : cls->get_dmethods()) { TRACE(CONSTP, 2, "dmethod: %s\n", dm->get_name()->c_str()); if (strcmp(dm->get_name()->c_str(), "propagation_1") == 0) { TRACE(CONSTP, 2, "dmethod: %s\n", SHOW(dm->get_code())); for (auto const instruction : dm->get_code()->get_instructions()) { ASSERT_NE(instruction->opcode(), OPCODE_IF_EQZ); ASSERT_NE(instruction->opcode(), OPCODE_NEW_INSTANCE); } } else if (strcmp(dm->get_name()->c_str(), "propagation_2") == 0) { TRACE(CONSTP, 2, "dmethod: %s\n", SHOW(dm->get_code())); for (auto const instruction : dm->get_code()->get_instructions()) { ASSERT_NE(instruction->opcode(), OPCODE_IF_EQZ); ASSERT_NE(instruction->opcode(), OPCODE_INVOKE_STATIC); } } else if(strcmp(dm->get_name()->c_str(), "propagation_3") == 0) { TRACE(CONSTP, 2, "dmethod: %s\n", SHOW(dm->get_code())); for (auto const instruction : dm->get_code()->get_instructions()) { //ASSERT_NE(instruction->opcode(), OPCODE_IF_EQZ); //ASSERT_NE(instruction->opcode(), OPCODE_INVOKE_STATIC); } } } } } }
DexClasses load_classes_from_dex(const char* location, bool balloon) { dex_stats_t stats; return load_classes_from_dex(location, &stats, balloon); }
TEST(EmptyClassesTest1, emptyclasses) { g_redex = new RedexContext(); const char* dexfile = "empty-classes-test-class.dex"; if (access(dexfile, R_OK) != 0) { dexfile = std::getenv("dexfile"); ASSERT_NE(nullptr, dexfile); } std::vector<DexClasses> dexen; dexen.emplace_back(load_classes_from_dex(dexfile)); DexClasses& classes = dexen.back(); size_t before = classes.size(); TRACE(EMPTY, 3, "Loaded classes: %ld\n", classes.size()); // Report the classes that were loaded through tracing. for (const auto& cls : classes) { TRACE(EMPTY, 3, "Input class: %s\n", cls->get_type()->get_name()->c_str()); } std::vector<Pass*> passes = { new DelInitPass(), new RemoveEmptyClassesPass(), }; std::vector<KeepRule> null_rules; auto const keep = { "Lcom/facebook/redextest/DoNotStrip;" }; const folly::dynamic conf_obj = folly::dynamic::object( "keep_annotations", folly::dynamic(keep.begin(), keep.end())); PassManager manager( passes, null_rules, conf_obj ); ConfigFiles dummy_cfg(conf_obj); manager.run_passes(dexen, dummy_cfg); size_t after = 0; std::set<std::string> remaining_classes; for (const auto& dex_classes : dexen) { for (const auto cls : dex_classes) { TRACE(EMPTY, 3, "Output class: %s\n", cls->get_type()->get_name()->c_str()); after++; remaining_classes.insert(SHOW(cls->get_type()->get_name())); } } TRACE(EMPTY, 2, "Removed %ld classes\n", before - after); ASSERT_EQ(0, remaining_classes.count("Lcom/facebook/redextest/EmptyClasses;")); ASSERT_EQ(0, remaining_classes.count("Lcom/facebook/redextest/InnerEmpty;")); ASSERT_EQ(0, remaining_classes.count("Lcom/facebook/redextest/InnerEmpty$InnerClass;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/InnerEmpty2;")); ASSERT_EQ(0, remaining_classes.count("Lcom/facebook/redextest/InnerEmpty2$InnerClass2;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass2;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass3;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass4;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass5;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotAnEmptyClass5;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/YesNo;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/MyYesNo;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/EasilyDone;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/By2Or3;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/MyBy2Or3;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/WombatException;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/Wombat;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/EmptyButLaterExtended;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/Extender;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/NotUsedHere;")); ASSERT_EQ(1, remaining_classes.count("Lcom/facebook/redextest/DontKillMeNow;")); delete g_redex; }