mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 06:05:15 -04:00
[Decompiler] Add offline tests (#303)
* add offline test for gcommon as an example. * fix test * unused var
This commit is contained in:
@@ -168,6 +168,7 @@ class Function {
|
||||
Form* top_form = nullptr;
|
||||
std::string debug_form_string;
|
||||
bool print_debug_forms = false;
|
||||
bool expressions_succeeded = false;
|
||||
} ir2;
|
||||
|
||||
private:
|
||||
|
||||
@@ -2241,10 +2241,14 @@ void ArrayFieldAccess::update_with_val(Form* new_val,
|
||||
auto sll_matcher =
|
||||
Matcher::fixed_op(FixedOperatorKind::SHL, {reg1_matcher, Matcher::integer(power_of_two)});
|
||||
sll_matcher = Matcher::match_or({Matcher::cast("uint", sll_matcher), sll_matcher});
|
||||
auto matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {reg0_matcher, sll_matcher});
|
||||
auto matcher = Matcher::match_or(
|
||||
{Matcher::fixed_op(FixedOperatorKind::ADDITION, {reg0_matcher, sll_matcher}),
|
||||
Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {reg0_matcher, sll_matcher})});
|
||||
auto match_result = match(matcher, new_val);
|
||||
if (!match_result.matched) {
|
||||
matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {sll_matcher, reg0_matcher});
|
||||
matcher = Matcher::match_or(
|
||||
{Matcher::fixed_op(FixedOperatorKind::ADDITION, {sll_matcher, reg0_matcher}),
|
||||
Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {sll_matcher, reg0_matcher})});
|
||||
match_result = match(matcher, new_val);
|
||||
if (!match_result.matched) {
|
||||
fmt::print("power {}\n", power_of_two);
|
||||
@@ -2405,7 +2409,6 @@ void TypeOfElement::update_from_stack(const Env& env,
|
||||
value->update_children_from_stack(env, pool, stack, allow_side_effects);
|
||||
result->push_back(this);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// EmptyElement
|
||||
////////////////////////
|
||||
@@ -2434,8 +2437,10 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
|
||||
// pop the value and the original
|
||||
auto popped = pop_to_forms({old_value, source}, env, pool, stack, true);
|
||||
if (!is_symbol_true(popped.at(0))) {
|
||||
throw std::runtime_error("Got unrecognized ConditionalMoveFalseElement original: " +
|
||||
popped.at(0)->to_string(env));
|
||||
lg::warn("Failed to ConditionalMoveFalseElement::push_to_stack");
|
||||
stack.push_value_to_reg(source, popped.at(1), true);
|
||||
stack.push_form_element(this, true);
|
||||
return;
|
||||
}
|
||||
stack.push_value_to_reg(dest,
|
||||
pool.alloc_single_element_form<GenericElement>(
|
||||
|
||||
@@ -78,7 +78,8 @@ class ObjectFileDB {
|
||||
void ir2_write_results(const std::string& output_dir);
|
||||
std::string ir2_to_file(ObjectFileData& data);
|
||||
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
|
||||
std::string ir2_final_out(ObjectFileData& data);
|
||||
std::string ir2_final_out(ObjectFileData& data,
|
||||
const std::unordered_set<std::string>& skip_functions = {});
|
||||
|
||||
void process_tpages();
|
||||
std::string process_game_count_file();
|
||||
|
||||
@@ -46,8 +46,11 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) {
|
||||
lg::info("Expression building...");
|
||||
ir2_build_expressions();
|
||||
}
|
||||
lg::info("Writing results...");
|
||||
ir2_write_results(output_dir);
|
||||
|
||||
if (!output_dir.empty()) {
|
||||
lg::info("Writing results...");
|
||||
ir2_write_results(output_dir);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -411,8 +414,7 @@ void ObjectFileDB::ir2_build_expressions() {
|
||||
if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func, dts)) {
|
||||
successful++;
|
||||
func.ir2.print_debug_forms = true;
|
||||
// auto end = final_defun_out(func, func.ir2.env, dts);
|
||||
// fmt::print("{}\n\n", end);
|
||||
func.ir2.expressions_succeeded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -782,14 +784,15 @@ bool ObjectFileDB::lookup_function_type(const FunctionName& name,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::ir2_final_out(ObjectFileData& data) {
|
||||
std::string ObjectFileDB::ir2_final_out(ObjectFileData& data,
|
||||
const std::unordered_set<std::string>& skip_functions) {
|
||||
if (data.obj_version == 3) {
|
||||
std::string result;
|
||||
result += ";;-*-Lisp-*-\n";
|
||||
result += "(in-package goal)\n\n";
|
||||
assert(data.linked_data.functions_by_seg.at(TOP_LEVEL_SEGMENT).size() == 1);
|
||||
auto top_level = data.linked_data.functions_by_seg.at(TOP_LEVEL_SEGMENT).at(0);
|
||||
result += write_from_top_level(top_level, dts, data.linked_data);
|
||||
result += write_from_top_level(top_level, dts, data.linked_data, skip_functions);
|
||||
result += "\n\n";
|
||||
return result;
|
||||
} else {
|
||||
|
||||
@@ -153,7 +153,8 @@ std::string careful_function_to_string(
|
||||
|
||||
std::string write_from_top_level(const Function& top_level,
|
||||
const DecompilerTypeSystem& dts,
|
||||
const LinkedObjectFile& file) {
|
||||
const LinkedObjectFile& file,
|
||||
const std::unordered_set<std::string>& skip_functions) {
|
||||
auto top_form = top_level.ir2.top_form;
|
||||
if (!top_form) {
|
||||
return ";; ERROR: top level function was not converted to expressions. Cannot decompile.\n\n";
|
||||
@@ -212,7 +213,11 @@ std::string write_from_top_level(const Function& top_level,
|
||||
something_matched = true;
|
||||
result += fmt::format(";; definition for function {}\n",
|
||||
global_match_result.maps.strings.at(func_name));
|
||||
result += careful_function_to_string(func, dts);
|
||||
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
|
||||
result += careful_function_to_string(func, dts);
|
||||
} else {
|
||||
result += ";; skipped.\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +229,11 @@ std::string write_from_top_level(const Function& top_level,
|
||||
something_matched = true;
|
||||
result += fmt::format(";; definition for method of type {}\n",
|
||||
method_match_result.maps.strings.at(type_name));
|
||||
result += careful_function_to_string(func, dts);
|
||||
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
|
||||
result += careful_function_to_string(func, dts);
|
||||
} else {
|
||||
result += ";; skipped.\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,8 +247,8 @@ std::string write_from_top_level(const Function& top_level,
|
||||
result += dts.ts.generate_deftype(dts.ts.lookup_type(name));
|
||||
result += "\n\n";
|
||||
} else {
|
||||
result +=
|
||||
fmt::format(";; type {} defintion, but it is unknown to the decompiler\n\n", name);
|
||||
result += fmt::format(
|
||||
";; type {} is defined here, but it is unknown to the decompiler\n\n", name);
|
||||
}
|
||||
something_matched = true;
|
||||
}
|
||||
@@ -256,7 +265,11 @@ std::string write_from_top_level(const Function& top_level,
|
||||
something_matched = true;
|
||||
result += fmt::format(";; definition (debug) for function {}\n",
|
||||
debug_match_result.maps.strings.at(0));
|
||||
result += careful_function_to_string(func, dts, FunctionDefSpecials::DEFUN_DEBUG);
|
||||
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
|
||||
result += careful_function_to_string(func, dts, FunctionDefSpecials::DEFUN_DEBUG);
|
||||
} else {
|
||||
result += ";; skipped.\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ std::string final_defun_out(const Function& func,
|
||||
FunctionDefSpecials special_mode = FunctionDefSpecials::NONE);
|
||||
std::string write_from_top_level(const Function& top_level,
|
||||
const DecompilerTypeSystem& dts,
|
||||
const LinkedObjectFile& file);
|
||||
const LinkedObjectFile& file,
|
||||
const std::unordered_set<std::string>& skip_functions = {});
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"gcommon":[
|
||||
["L345", "float", true]
|
||||
],
|
||||
|
||||
"math":[
|
||||
["L41", "float", true],
|
||||
["L34", "float", true],
|
||||
|
||||
@@ -288,11 +288,26 @@ bool Compiler::connect_to_target() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Just run the front end on a string. Will not do register allocation or code generation.
|
||||
* Useful for typechecking or running strings that invoke the compiler again.
|
||||
*/
|
||||
void Compiler::run_front_end_on_string(const std::string& src) {
|
||||
auto code = m_goos.reader.read_from_string({src});
|
||||
compile_object_file("run-on-string", code, true);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Run the entire compilation process on the input source code. Will generate an object file, but
|
||||
* won't save it anywhere.
|
||||
*/
|
||||
void Compiler::run_full_compiler_on_string_no_save(const std::string& src) {
|
||||
auto code = m_goos.reader.read_from_string({src});
|
||||
auto compiled = compile_object_file("run-on-string", code, true);
|
||||
color_object_file(compiled);
|
||||
codegen_object_file(compiled);
|
||||
}
|
||||
|
||||
std::vector<std::string> Compiler::run_test_no_load(const std::string& source_code) {
|
||||
auto code = m_goos.reader.read_from_file({source_code});
|
||||
compile_object_file("test-code", code, true);
|
||||
|
||||
@@ -38,6 +38,7 @@ class Compiler {
|
||||
std::vector<std::string> run_test_no_load(const std::string& source_code);
|
||||
void compile_and_send_from_string(const std::string& source_code);
|
||||
void run_front_end_on_string(const std::string& src);
|
||||
void run_full_compiler_on_string_no_save(const std::string& src);
|
||||
void shutdown_target();
|
||||
void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; }
|
||||
Debugger& get_debugger() { return m_debugger; }
|
||||
|
||||
@@ -197,7 +197,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
|
||||
/*!
|
||||
* Simple help / documentation command
|
||||
*/
|
||||
Val* Compiler::compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||
Val* Compiler::compile_repl_help(const goos::Object&, const goos::Object&, Env*) {
|
||||
fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n");
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n");
|
||||
fmt::print(" - Exit the compiler once the current REPL command is finished\n");
|
||||
|
||||
+3
-3
@@ -4,8 +4,8 @@
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
#include "third-party/fmt/core.h";
|
||||
#include "third-party/fmt/color.h";
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "third-party/fmt/color.h"
|
||||
|
||||
#include "common/goos/ReplHistory.h"
|
||||
|
||||
@@ -61,7 +61,7 @@ int main(int argc, char** argv) {
|
||||
fmt::print(" for help with common commands and REPL usage.\n");
|
||||
fmt::print("Run ");
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)");
|
||||
fmt::print(" to connect to the local listener.\n");
|
||||
fmt::print(" to connect to the local target.\n");
|
||||
|
||||
ReplHistory::repl_load_history();
|
||||
if (argument.empty()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/goalc/CMakeLists.txt)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/offline/CMakeLists.txt)
|
||||
|
||||
add_executable(goalc-test
|
||||
${CMAKE_CURRENT_LIST_DIR}/test_main.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2572,4 +2572,25 @@ TEST_F(FormRegressionTest, ExprTerminal2) {
|
||||
" (- f0-4 (+ arg1 (* arg2 (* f0-4 f0-4))))\n"
|
||||
" )";
|
||||
test_with_expr(func, type, expected, false, "", {{"L17", "A ~A"}});
|
||||
}
|
||||
|
||||
TEST_F(FormRegressionTest, MoveFalse) {
|
||||
std::string func =
|
||||
"sll r0, r0, 0\n"
|
||||
"L29:\n"
|
||||
" daddiu sp, sp, -16\n"
|
||||
" sd fp, 8(sp)\n"
|
||||
" or fp, t9, r0\n"
|
||||
" daddiu v1, s7, 8\n"
|
||||
" daddiu a0, a0, 12\n"
|
||||
" andi a0, a0, 1\n"
|
||||
" movz v1, s7, a0\n"
|
||||
" or v0, v1, r0\n"
|
||||
" ld fp, 8(sp)\n"
|
||||
" jr ra\n"
|
||||
" daddiu sp, sp, 16\n";
|
||||
std::string type = "(function int symbol)";
|
||||
|
||||
std::string expected = "(nonzero? (logand (+ arg0 12) 1))";
|
||||
test_with_expr(func, type, expected, false, "", {{"L17", "A ~A"}});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
add_executable(offline-test
|
||||
${CMAKE_CURRENT_LIST_DIR}/offline_test_main.cpp)
|
||||
|
||||
target_link_libraries(offline-test common gtest decomp compiler)
|
||||
@@ -0,0 +1,303 @@
|
||||
#include <common/link_types.h>
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "common/log/log.h"
|
||||
#include "decompiler/Disasm/OpcodeInfo.h"
|
||||
#include "decompiler/config.h"
|
||||
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
||||
#include "goalc/compiler/Compiler.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
lg::initialize();
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// the object files to test
|
||||
const std::unordered_set<std::string> object_files_to_decompile = {"gcommon"};
|
||||
|
||||
// the object files to check against a reference in test/decompiler/reference
|
||||
const std::unordered_set<std::string> object_files_to_check_against_reference = {
|
||||
"gcommon" // NOTE: this file needs work, but adding it for now just to test the framework.
|
||||
};
|
||||
|
||||
// the functions we expect the decompiler to skip
|
||||
const std::unordered_set<std::string> expected_skip_in_decompiler = {
|
||||
// gcommon
|
||||
"quad-copy!", // asm mempcy
|
||||
// gkernel
|
||||
"set-to-run-bootstrap", // kernel context switch
|
||||
"throw", // manually sets fp/t9.
|
||||
"throw-dispatch", // restore context
|
||||
"(method 0 catch-frame)", // save context
|
||||
"(method 11 cpu-thread)", // kernel -> user context switch
|
||||
"(method 10 cpu-thread)", // user -> kernel context switch
|
||||
"reset-and-call", // kernel -> user
|
||||
"return-from-thread-dead", // kernel -> user
|
||||
"return-from-thread", // kernel -> user
|
||||
"return-from-exception", // ps2 exception -> ps2 user
|
||||
// pskernel
|
||||
"kernel-check-hardwired-addresses", // ps2 ee kernel debug hook
|
||||
"kernel-read-function", // ps2 ee kernel debug hook
|
||||
"kernel-write-function", // ps2 ee kernel debug hook
|
||||
"kernel-copy-function" // ps2 ee kernel debug hook
|
||||
};
|
||||
|
||||
const std::unordered_set<std::string> skip_in_compiling = {
|
||||
// these functions are not implemented by the compiler in OpenGOAL, but are in GOAL.
|
||||
"abs", "ash", "min", "max", "lognor"};
|
||||
|
||||
} // namespace
|
||||
|
||||
class OfflineDecompilation : public ::testing::Test {
|
||||
protected:
|
||||
static std::unique_ptr<decompiler::ObjectFileDB> db;
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// global setup
|
||||
file_util::init_crc();
|
||||
decompiler::init_opcode_info();
|
||||
decompiler::set_config(
|
||||
file_util::get_file_path({"decompiler", "config", "jak1_ntsc_black_label.jsonc"}));
|
||||
|
||||
decompiler::get_config().allowed_objects = object_files_to_decompile;
|
||||
db = std::make_unique<decompiler::ObjectFileDB>(
|
||||
std::vector<std::string>{file_util::get_file_path({"iso_data", "CGO", "KERNEL.CGO"}),
|
||||
file_util::get_file_path({"iso_data", "CGO", "ENGINE.CGO"})},
|
||||
decompiler::get_config().obj_file_name_map_file, std::vector<std::string>{},
|
||||
std::vector<std::string>{});
|
||||
|
||||
// basic processing to find functions/data/disassembly
|
||||
db->process_link_data();
|
||||
db->find_code();
|
||||
db->process_labels();
|
||||
|
||||
// fancy decompilation.
|
||||
db->analyze_functions_ir2({});
|
||||
}
|
||||
|
||||
static void TearDownTestCase() { db.reset(); }
|
||||
};
|
||||
|
||||
std::unique_ptr<decompiler::ObjectFileDB> OfflineDecompilation::db;
|
||||
|
||||
/*!
|
||||
* Check that the most basic disassembly into files/functions/instructions has succeeded.
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, CheckBasicDecode) {
|
||||
int obj_count = 0;
|
||||
db->for_each_obj([&](decompiler::ObjectFileData& obj) {
|
||||
obj_count++;
|
||||
auto& stats = obj.linked_data.stats;
|
||||
// make sure we decoded all instructions
|
||||
EXPECT_EQ(stats.code_bytes / 4, stats.decoded_ops);
|
||||
// make sure all FP uses are properly recognized
|
||||
EXPECT_EQ(stats.n_fp_reg_use, stats.n_fp_reg_use_resolved);
|
||||
});
|
||||
|
||||
EXPECT_EQ(obj_count, decompiler::get_config().allowed_objects.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Not a super great test, but check that we find functions, methods, and logins.
|
||||
* This is a test of ir2_top_level_pass, which isn't tested as part of the normal decompiler tests.
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, FunctionDetect) {
|
||||
int function_count = 0; // global functions
|
||||
int method_count = 0; // methods
|
||||
int login_count = 0; // top-level logins
|
||||
int unknown_count = 0; // unknown functions, like anonymous lambdas
|
||||
|
||||
db->for_each_function(
|
||||
[&](decompiler::Function& func, int segment_id, decompiler::ObjectFileData&) {
|
||||
if (segment_id == TOP_LEVEL_SEGMENT) {
|
||||
EXPECT_EQ(func.guessed_name.kind, decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT);
|
||||
} else {
|
||||
EXPECT_NE(func.guessed_name.kind, decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT);
|
||||
}
|
||||
switch (func.guessed_name.kind) {
|
||||
case decompiler::FunctionName::FunctionKind::GLOBAL:
|
||||
function_count++;
|
||||
break;
|
||||
case decompiler::FunctionName::FunctionKind::METHOD:
|
||||
method_count++;
|
||||
break;
|
||||
case decompiler::FunctionName::FunctionKind::TOP_LEVEL_INIT:
|
||||
login_count++;
|
||||
break;
|
||||
case decompiler::FunctionName::FunctionKind::UNIDENTIFIED:
|
||||
unknown_count++;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
});
|
||||
|
||||
// one login per object file
|
||||
EXPECT_EQ(decompiler::get_config().allowed_objects.size(), login_count);
|
||||
|
||||
// not many lambdas.
|
||||
EXPECT_TRUE(unknown_count < 10);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, AsmFunction) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (func.suspected_asm) {
|
||||
if (expected_skip_in_decompiler.find(func.guessed_name.to_string()) ==
|
||||
expected_skip_in_decompiler.end()) {
|
||||
lg::error("Function {} was marked as asm, but wasn't expected.",
|
||||
func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass CFG build stage.
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, CfgBuild) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.cfg || !func.cfg->is_fully_resolved()) {
|
||||
lg::error("Function {} failed cfg", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass the atomic op construction stage
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, AtomicOp) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.atomic_ops || !func.ir2.atomic_ops_succeeded) {
|
||||
lg::error("Function {} failed atomic ops", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test that all functions pass the type analysis stage
|
||||
*/
|
||||
TEST_F(OfflineDecompilation, TypeAnalysis) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_type_analysis()) {
|
||||
lg::error("Function {} failed types", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, RegisterUse) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_reg_use()) {
|
||||
lg::error("Function {} failed reg use", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, VariableSSA) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.env.has_local_vars()) {
|
||||
lg::error("Function {} failed ssa", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, Structuring) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.top_form) {
|
||||
lg::error("Function {} failed structuring", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, Expressions) {
|
||||
int failed_count = 0;
|
||||
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
|
||||
if (!func.suspected_asm) {
|
||||
if (!func.ir2.expressions_succeeded) {
|
||||
lg::error("Function {} failed expressions", func.guessed_name.to_string());
|
||||
failed_count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(failed_count, 0);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void strip_trailing_newlines(std::string& in) {
|
||||
while (!in.empty() && in.back() == '\n') {
|
||||
in.pop_back();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(OfflineDecompilation, Reference) {
|
||||
for (auto& file : object_files_to_check_against_reference) {
|
||||
auto& obj_l = db->obj_files_by_name.at(file);
|
||||
ASSERT_EQ(obj_l.size(), 1);
|
||||
|
||||
std::string src = db->ir2_final_out(obj_l.at(0));
|
||||
|
||||
auto reference = file_util::read_text_file(file_util::get_file_path(
|
||||
{"test", "decompiler", "reference", fmt::format("{}_REF.gc", file)}));
|
||||
|
||||
strip_trailing_newlines(reference);
|
||||
strip_trailing_newlines(src);
|
||||
|
||||
EXPECT_EQ(reference, src);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(OfflineDecompilation, Compile) {
|
||||
Compiler compiler;
|
||||
|
||||
for (auto& file : object_files_to_check_against_reference) {
|
||||
auto& obj_l = db->obj_files_by_name.at(file);
|
||||
ASSERT_EQ(obj_l.size(), 1);
|
||||
|
||||
std::string src = db->ir2_final_out(obj_l.at(0), skip_in_compiling);
|
||||
|
||||
compiler.run_full_compiler_on_string_no_save(src);
|
||||
}
|
||||
}
|
||||
+4
-20
@@ -1,11 +1,9 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
#include <filesystem>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include "common/log/log.h"
|
||||
|
||||
// Running subsets of tests, see:
|
||||
// -
|
||||
@@ -19,22 +17,8 @@
|
||||
// to make it easier to test a subset of tests
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#ifdef _WIN32
|
||||
// Always enable VIRTUAL_TERMINAL_PROCESSING, this console mode allows the console (stdout) to
|
||||
// support ANSI colors in the outputted text, which are used by the logging tool.
|
||||
// This mode may not be enabled by default, and changing that involves modifying the registry,
|
||||
// so it seems like a better solution would be enabling it ourselves.
|
||||
lg::initialize();
|
||||
|
||||
// Get handle to stdout
|
||||
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
// get current stdout mode
|
||||
DWORD modeStdOut;
|
||||
GetConsoleMode(hStdOut, &modeStdOut);
|
||||
// enable VIRTUAL_TERMINAL_PROCESSING. As a bitwise OR it will not do anything if it is
|
||||
// already set
|
||||
modeStdOut |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hStdOut, modeStdOut);
|
||||
#endif
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// Re-init failed folder
|
||||
|
||||
Reference in New Issue
Block a user