Пример #1
0
/*
 * 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);
}
Пример #2
0
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);
        }
      }
    }
  }

}
Пример #3
0
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();
    }
  }
}
Пример #4
0
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;
}
Пример #5
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);
}
Пример #6
0
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));
  }
}
Пример #7
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(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);
              }
			  }
			}
		}
	}
}
Пример #8
0
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;
}
Пример #9
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);
          }
        }
      }
    }
  }
}
Пример #10
0
DexClasses load_classes_from_dex(const char* location, bool balloon) {
  dex_stats_t stats;
  return load_classes_from_dex(location, &stats, balloon);
}
Пример #11
0
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;
}