2021-01-23 16:32:56 -05:00
|
|
|
#include "FormRegressionTest.h"
|
|
|
|
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "common/goos/PrettyPrinter.h"
|
|
|
|
#include "common/util/json_util.h"
|
|
|
|
|
|
|
|
#include "decompiler/IR2/Form.h"
|
2021-01-23 16:32:56 -05:00
|
|
|
#include "decompiler/analysis/cfg_builder.h"
|
|
|
|
#include "decompiler/analysis/expression_build.h"
|
2021-02-05 19:41:09 -05:00
|
|
|
#include "decompiler/analysis/final_output.h"
|
2021-07-25 15:30:37 -04:00
|
|
|
#include "decompiler/analysis/inline_asm_rewrite.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "decompiler/analysis/insert_lets.h"
|
|
|
|
#include "decompiler/analysis/reg_usage.h"
|
|
|
|
#include "decompiler/analysis/stack_spill.h"
|
|
|
|
#include "decompiler/analysis/type_analysis.h"
|
|
|
|
#include "decompiler/analysis/variable_naming.h"
|
2021-03-27 15:18:59 -04:00
|
|
|
#include "decompiler/util/config_parsers.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
|
2021-02-01 20:41:37 -05:00
|
|
|
#include "third-party/json.hpp"
|
2021-01-23 16:32:56 -05:00
|
|
|
|
|
|
|
using namespace decompiler;
|
|
|
|
|
2022-08-05 12:12:33 -04:00
|
|
|
void FormRegressionTestJak1::SetUpTestCase() {
|
2021-01-23 16:32:56 -05:00
|
|
|
parser = std::make_unique<InstructionParser>();
|
2022-08-05 12:12:33 -04:00
|
|
|
dts = std::make_unique<DecompilerTypeSystem>(game_version);
|
2021-01-23 16:32:56 -05:00
|
|
|
dts->parse_type_defs({"decompiler", "config", "all-types.gc"});
|
|
|
|
}
|
|
|
|
|
2022-08-05 12:12:33 -04:00
|
|
|
void FormRegressionTestJak2::SetUpTestCase() {
|
|
|
|
parser = std::make_unique<InstructionParser>();
|
|
|
|
dts = std::make_unique<DecompilerTypeSystem>(game_version);
|
|
|
|
dts->parse_type_defs({"decompiler", "config", "jak2", "all-types.gc"});
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:32:56 -05:00
|
|
|
void FormRegressionTest::TearDownTestCase() {
|
|
|
|
parser.reset();
|
|
|
|
dts.reset();
|
|
|
|
parser.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormRegressionTest::TestData::add_string_at_label(const std::string& label_name,
|
|
|
|
const std::string& data) {
|
|
|
|
// first, align segment 1:
|
|
|
|
while (file.words_by_seg.at(1).size() % 4) {
|
|
|
|
file.words_by_seg.at(1).push_back(LinkedWord(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
// add string type tag:
|
|
|
|
LinkedWord type_tag(0);
|
2021-12-04 12:33:18 -05:00
|
|
|
type_tag.set_to_symbol(decompiler::LinkedWord::TYPE_PTR, "string");
|
2021-01-23 16:32:56 -05:00
|
|
|
file.words_by_seg.at(1).push_back(type_tag);
|
|
|
|
int string_start = 4 * int(file.words_by_seg.at(1).size());
|
|
|
|
|
|
|
|
// add size
|
|
|
|
file.words_by_seg.at(1).push_back(LinkedWord(int(data.length())));
|
|
|
|
|
|
|
|
// add string:
|
|
|
|
std::vector<char> bytes;
|
|
|
|
bytes.resize(((data.size() + 1 + 3) / 4) * 4);
|
|
|
|
for (size_t i = 0; i < data.size(); i++) {
|
|
|
|
bytes[i] = data[i];
|
|
|
|
}
|
|
|
|
for (size_t i = 0; i < bytes.size() / 4; i++) {
|
|
|
|
auto word = ((uint32_t*)bytes.data())[i];
|
|
|
|
file.words_by_seg.at(1).push_back(LinkedWord(word));
|
|
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
file.words_by_seg.at(1).push_back(LinkedWord(0));
|
|
|
|
}
|
|
|
|
// will be already null terminated.
|
|
|
|
|
|
|
|
for (auto& label : file.labels) {
|
|
|
|
if (label.name == label_name) {
|
|
|
|
label.target_segment = 1;
|
|
|
|
label.offset = string_start;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPECT_TRUE(false);
|
|
|
|
}
|
|
|
|
|
2021-03-13 16:10:39 -05:00
|
|
|
std::pair<std::vector<std::string>, std::unordered_map<std::string, LocalVarOverride>>
|
|
|
|
parse_var_json(const std::string& str) {
|
|
|
|
if (str.empty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> args;
|
|
|
|
std::unordered_map<std::string, LocalVarOverride> var_overrides;
|
|
|
|
|
2021-05-11 21:57:05 -04:00
|
|
|
auto j = parse_commented_json(str, "Regression Test variable map");
|
2021-03-13 16:10:39 -05:00
|
|
|
|
|
|
|
auto arg = j.find("args");
|
|
|
|
if (arg != j.end()) {
|
|
|
|
for (auto& x : arg.value()) {
|
|
|
|
args.push_back(x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto var = j.find("vars");
|
|
|
|
if (var != j.end()) {
|
|
|
|
for (auto& vkv : var->get<std::unordered_map<std::string, nlohmann::json>>()) {
|
|
|
|
LocalVarOverride override;
|
|
|
|
if (vkv.second.is_string()) {
|
|
|
|
override.name = vkv.second.get<std::string>();
|
|
|
|
} else if (vkv.second.is_array()) {
|
|
|
|
override.name = vkv.second[0].get<std::string>();
|
|
|
|
override.type = vkv.second[1].get<std::string>();
|
|
|
|
} else {
|
|
|
|
throw std::runtime_error("Invalid function var override.");
|
|
|
|
}
|
|
|
|
var_overrides[vkv.first] = override;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {args, var_overrides};
|
|
|
|
}
|
|
|
|
|
2021-01-23 16:32:56 -05:00
|
|
|
std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
|
|
|
const std::string& code,
|
|
|
|
const TypeSpec& function_type,
|
2021-03-27 15:18:59 -04:00
|
|
|
const TestSettings& settings) {
|
|
|
|
// Set up decompiler type system
|
2021-01-23 16:32:56 -05:00
|
|
|
dts->type_prop_settings.reset();
|
2021-03-27 15:18:59 -04:00
|
|
|
dts->type_prop_settings.current_method_type = settings.method_name;
|
2021-02-01 20:41:37 -05:00
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// set up label names for string constants
|
2021-02-01 20:41:37 -05:00
|
|
|
std::vector<std::string> string_label_names;
|
2021-03-27 15:18:59 -04:00
|
|
|
for (auto& x : settings.strings) {
|
2021-02-01 20:41:37 -05:00
|
|
|
string_label_names.push_back(x.first);
|
|
|
|
}
|
2021-03-27 15:18:59 -04:00
|
|
|
|
|
|
|
// parse the assembly
|
2021-02-01 20:41:37 -05:00
|
|
|
auto program = parser->parse_program(code, string_label_names);
|
2021-03-27 15:18:59 -04:00
|
|
|
|
|
|
|
// create the test data collection
|
2022-06-25 21:26:15 -04:00
|
|
|
auto test = std::make_unique<TestData>(program.instructions.size(), settings.version);
|
2021-03-27 15:18:59 -04:00
|
|
|
// populate the LinkedObjectFile
|
2021-01-23 16:32:56 -05:00
|
|
|
test->file.words_by_seg.resize(3);
|
|
|
|
test->file.labels = program.labels;
|
2021-03-27 15:18:59 -04:00
|
|
|
// Set up the environment
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.ir2.env.file = &test->file;
|
2021-01-24 16:39:15 -05:00
|
|
|
test->func.ir2.env.dts = dts.get();
|
2021-05-11 16:43:13 -04:00
|
|
|
test->func.ir2.env.func = &test->func;
|
2021-03-27 15:18:59 -04:00
|
|
|
// Set up the function
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.instructions = program.instructions;
|
|
|
|
test->func.guessed_name.set_as_global("test-function");
|
|
|
|
test->func.type = function_type;
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// set up string constants in the data
|
|
|
|
for (auto& str : settings.strings) {
|
2021-01-23 16:32:56 -05:00
|
|
|
test->add_string_at_label(str.first, str.second);
|
|
|
|
}
|
|
|
|
|
2021-08-29 11:13:06 -04:00
|
|
|
// set up LabelDB:
|
|
|
|
test->file.label_db = std::make_unique<LabelDB>(
|
|
|
|
std::unordered_map<std::string, LabelConfigInfo>{}, test->file.labels, *dts);
|
|
|
|
|
|
|
|
for (auto& str : settings.strings) {
|
|
|
|
test->file.label_db->set_and_get_previous(test->file.label_db->get_index_by_name(str.first),
|
|
|
|
TypeSpec("string"), false, {});
|
|
|
|
}
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// find basic blocks
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.basic_blocks = find_blocks_in_function(test->file, 0, test->func);
|
2021-03-27 15:18:59 -04:00
|
|
|
// analyze function prologue/epilogue
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.analyze_prologue(test->file);
|
2021-03-27 15:18:59 -04:00
|
|
|
// build control flow graph
|
2022-06-25 21:26:15 -04:00
|
|
|
test->func.cfg = build_cfg(test->file, 0, test->func, {}, {}, settings.version);
|
2021-01-23 16:32:56 -05:00
|
|
|
EXPECT_TRUE(test->func.cfg->is_fully_resolved());
|
2021-02-16 20:37:48 -05:00
|
|
|
if (!test->func.cfg->is_fully_resolved()) {
|
|
|
|
fmt::print("CFG:\n{}\n", test->func.cfg->to_dot());
|
2021-05-12 15:54:33 -04:00
|
|
|
} else {
|
|
|
|
test->func.cfg_ok = true;
|
2021-02-16 20:37:48 -05:00
|
|
|
}
|
2021-01-23 16:32:56 -05:00
|
|
|
|
2021-04-25 14:48:54 -04:00
|
|
|
// find stack spill slots
|
|
|
|
auto spill_map = build_spill_map(test->func.instructions,
|
|
|
|
{test->func.prologue_end, test->func.epilogue_start});
|
|
|
|
test->func.ir2.env.set_stack_spills(spill_map);
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// convert instruction to atomic ops
|
2021-03-25 16:02:48 -04:00
|
|
|
DecompWarnings warnings;
|
2022-06-08 18:34:52 -04:00
|
|
|
auto ops = convert_function_to_atomic_ops(test->func, program.labels, warnings, false, {},
|
2022-06-25 21:26:15 -04:00
|
|
|
settings.version);
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
|
|
|
test->func.ir2.atomic_ops_succeeded = true;
|
2021-01-25 22:08:58 -05:00
|
|
|
test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var());
|
2021-01-23 16:32:56 -05:00
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// set up type settings
|
|
|
|
if (!settings.casts_json.empty()) {
|
|
|
|
test->func.ir2.env.set_type_casts(parse_cast_hints(nlohmann::json::parse(settings.casts_json)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.allow_pairs) {
|
|
|
|
test->func.ir2.env.set_sloppy_pair_typing();
|
|
|
|
}
|
|
|
|
|
2021-06-04 13:43:19 -04:00
|
|
|
if (!settings.stack_structure_json.empty()) {
|
|
|
|
auto stack_hints =
|
|
|
|
parse_stack_structure_hints(nlohmann::json::parse(settings.stack_structure_json));
|
|
|
|
test->func.ir2.env.set_stack_structure_hints(stack_hints);
|
2021-03-27 15:18:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// analyze types
|
|
|
|
EXPECT_TRUE(run_type_analysis_ir2(function_type, *dts, test->func));
|
2021-03-24 19:16:31 -04:00
|
|
|
test->func.ir2.env.types_succeeded = true;
|
2021-01-23 16:32:56 -05:00
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// analyze registers
|
2021-01-23 16:32:56 -05:00
|
|
|
test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func));
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// split to variables
|
2021-01-23 16:32:56 -05:00
|
|
|
auto result = run_variable_renaming(test->func, test->func.ir2.env.reg_use(),
|
|
|
|
*test->func.ir2.atomic_ops, *dts);
|
|
|
|
if (result.has_value()) {
|
|
|
|
test->func.ir2.env.set_local_vars(*result);
|
|
|
|
} else {
|
|
|
|
EXPECT_TRUE(false);
|
|
|
|
}
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// structure
|
2021-01-23 16:32:56 -05:00
|
|
|
build_initial_forms(test->func);
|
|
|
|
EXPECT_TRUE(test->func.ir2.top_form);
|
|
|
|
|
2021-01-24 16:39:15 -05:00
|
|
|
if (test->func.ir2.top_form) {
|
2021-03-27 15:18:59 -04:00
|
|
|
// just make sure this doesn't crash
|
2021-02-28 12:38:29 -05:00
|
|
|
RegAccessSet vars;
|
2021-03-05 18:48:01 -05:00
|
|
|
test->func.ir2.top_form->collect_vars(vars, true);
|
2021-01-24 16:39:15 -05:00
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
if (settings.do_expressions) {
|
|
|
|
auto config = parse_var_json(settings.var_map_json);
|
|
|
|
// build expressions (most of the fancy decompilation happens here)
|
2021-01-24 16:39:15 -05:00
|
|
|
bool success = convert_to_expressions(test->func.ir2.top_form, *test->func.ir2.form_pool,
|
2021-03-13 16:10:39 -05:00
|
|
|
test->func, config.first, config.second, *dts);
|
2021-01-24 16:39:15 -05:00
|
|
|
|
|
|
|
EXPECT_TRUE(success);
|
|
|
|
if (!success) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-07-25 15:30:37 -04:00
|
|
|
|
|
|
|
rewrite_inline_asm_instructions(test->func.ir2.top_form, *test->func.ir2.form_pool,
|
|
|
|
test->func, *dts);
|
|
|
|
|
2021-03-27 15:18:59 -04:00
|
|
|
// move variables into lets.
|
2022-05-19 21:30:14 -04:00
|
|
|
LetRewriteStats dummy;
|
2021-03-05 18:48:01 -05:00
|
|
|
insert_lets(test->func, test->func.ir2.env, *test->func.ir2.form_pool,
|
2022-05-19 21:30:14 -04:00
|
|
|
test->func.ir2.top_form, dummy);
|
2021-01-23 16:32:56 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 16:39:15 -05:00
|
|
|
// for (int i = 0; i < int(test->func.ir2.atomic_ops->ops.size()); i++) {
|
|
|
|
// auto& op = test->func.ir2.atomic_ops->ops.at(i);
|
|
|
|
// auto& info = test->func.ir2.env.reg_use().op.at(i);
|
|
|
|
// fmt::print("{} - {}: ", op->to_string(test->func.ir2.env),
|
|
|
|
// test->func.ir2.env.get_types_after_op(i).print_gpr_masked(
|
|
|
|
// regs_to_gpr_mask({Register(Reg::GPR, Reg::V0)})));
|
|
|
|
// for (auto live : info.live) {
|
|
|
|
// fmt::print("{} ", live.to_charp());
|
|
|
|
// }
|
|
|
|
// fmt::print("\n");
|
|
|
|
// }
|
|
|
|
|
2021-01-23 16:32:56 -05:00
|
|
|
return test;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormRegressionTest::test(const std::string& code,
|
|
|
|
const std::string& type,
|
|
|
|
const std::string& expected,
|
2021-03-27 15:18:59 -04:00
|
|
|
const TestSettings& settings) {
|
2021-01-23 16:32:56 -05:00
|
|
|
auto ts = dts->parse_type_spec(type);
|
2021-03-27 15:18:59 -04:00
|
|
|
auto test = make_function(code, ts, settings);
|
2021-01-23 16:32:56 -05:00
|
|
|
ASSERT_TRUE(test);
|
|
|
|
auto expected_form =
|
|
|
|
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
|
2021-01-24 16:39:15 -05:00
|
|
|
ASSERT_TRUE(test->func.ir2.top_form);
|
2021-01-23 16:32:56 -05:00
|
|
|
auto actual_form =
|
|
|
|
pretty_print::get_pretty_printer_reader()
|
|
|
|
.read_from_string(test->func.ir2.top_form->to_form(test->func.ir2.env).print(), false)
|
|
|
|
.as_pair()
|
|
|
|
->car;
|
|
|
|
if (expected_form != actual_form) {
|
2021-01-24 16:39:15 -05:00
|
|
|
printf("Got:\n%s\n\nExpected\n%s\n", pretty_print::to_string(actual_form).c_str(),
|
|
|
|
pretty_print::to_string(expected_form).c_str());
|
2021-01-23 16:32:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPECT_TRUE(expected_form == actual_form);
|
|
|
|
}
|
|
|
|
|
2022-08-05 12:12:33 -04:00
|
|
|
void FormRegressionTest::test_final_function(
|
2021-02-05 19:41:09 -05:00
|
|
|
const std::string& code,
|
|
|
|
const std::string& type,
|
|
|
|
const std::string& expected,
|
|
|
|
bool allow_pairs,
|
|
|
|
const std::vector<std::pair<std::string, std::string>>& strings,
|
2021-03-27 15:18:59 -04:00
|
|
|
const std::string& cast_json,
|
2021-03-13 16:10:39 -05:00
|
|
|
const std::string& var_map_json) {
|
2021-02-05 19:41:09 -05:00
|
|
|
auto ts = dts->parse_type_spec(type);
|
2021-03-27 15:18:59 -04:00
|
|
|
TestSettings settings;
|
|
|
|
settings.allow_pairs = allow_pairs;
|
|
|
|
settings.strings = strings;
|
|
|
|
settings.casts_json = cast_json;
|
|
|
|
settings.var_map_json = var_map_json;
|
|
|
|
settings.do_expressions = true;
|
|
|
|
auto test = make_function(code, ts, settings);
|
2021-02-05 19:41:09 -05:00
|
|
|
ASSERT_TRUE(test);
|
|
|
|
auto expected_form =
|
|
|
|
pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car;
|
|
|
|
ASSERT_TRUE(test->func.ir2.top_form);
|
|
|
|
auto final = final_defun_out(test->func, test->func.ir2.env, *dts);
|
|
|
|
auto actual_form =
|
|
|
|
pretty_print::get_pretty_printer_reader().read_from_string(final, false).as_pair()->car;
|
|
|
|
if (expected_form != actual_form) {
|
|
|
|
printf("Got:\n%s\n\nExpected\n%s\n", pretty_print::to_string(actual_form).c_str(),
|
|
|
|
pretty_print::to_string(expected_form).c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPECT_TRUE(expected_form == actual_form);
|
|
|
|
}
|
|
|
|
|
2022-08-05 12:12:33 -04:00
|
|
|
void FormRegressionTest::test_with_stack_structures(const std::string& code,
|
|
|
|
const std::string& type,
|
|
|
|
const std::string& expected,
|
|
|
|
const std::string& stack_map_json,
|
|
|
|
const std::string& cast_json,
|
|
|
|
const std::string& var_map_json) {
|
2021-03-27 15:18:59 -04:00
|
|
|
TestSettings settings;
|
|
|
|
settings.do_expressions = true;
|
2021-06-04 13:43:19 -04:00
|
|
|
settings.stack_structure_json = stack_map_json;
|
2021-03-27 15:18:59 -04:00
|
|
|
settings.var_map_json = var_map_json;
|
|
|
|
settings.casts_json = cast_json;
|
|
|
|
test(code, type, expected, settings);
|
2021-02-01 20:41:37 -05:00
|
|
|
}
|
|
|
|
|
2021-01-23 16:32:56 -05:00
|
|
|
std::unique_ptr<InstructionParser> FormRegressionTest::parser;
|
2022-05-19 21:30:14 -04:00
|
|
|
std::unique_ptr<DecompilerTypeSystem> FormRegressionTest::dts;
|