| // Copyright © 2024 Rot127 <unisono@quyllur.org> |
| // SPDX-License-Identifier: BSD-3 |
| |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <setjmp.h> |
| #include "cmocka.h" |
| #include "test_detail.h" |
| #include "test_case.h" |
| #include "test_compare.h" |
| #include "helper.h" |
| #include "../../../utils.h" |
| #include <stdio.h> |
| #include <string.h> |
| |
| TestInput *test_input_new() |
| { |
| TestInput *p = cs_mem_calloc(sizeof(TestInput), 1); |
| assert(p); |
| return p; |
| } |
| |
| void test_input_free(TestInput *test_input) |
| { |
| if (!test_input) { |
| return; |
| } |
| cs_mem_free(test_input->name); |
| cs_mem_free(test_input->bytes); |
| cs_mem_free(test_input->arch); |
| for (size_t i = 0; i < test_input->options_count; i++) { |
| cs_mem_free(test_input->options[i]); |
| } |
| cs_mem_free(test_input->options); |
| cs_mem_free(test_input); |
| } |
| |
| TestInput *test_input_clone(TestInput *test_input) |
| { |
| assert(test_input); |
| TestInput *ti = test_input_new(); |
| ti->address = test_input->address; |
| |
| for (size_t i = 0; i < test_input->options_count; i++) { |
| ti->options = cs_mem_realloc( |
| ti->options, sizeof(char *) * (ti->options_count + 1)); |
| ti->options[i] = cs_strdup(test_input->options[i]); |
| ti->options_count++; |
| } |
| ti->name = test_input->name ? cs_strdup(test_input->name) : NULL; |
| ti->arch = cs_strdup(test_input->arch); |
| ti->bytes = cs_mem_calloc(sizeof(uint8_t), test_input->bytes_count); |
| ti->bytes_count = test_input->bytes_count; |
| memcpy(ti->bytes, test_input->bytes, test_input->bytes_count); |
| return ti; |
| } |
| |
| char *test_input_stringify(const TestInput *test_input, const char *postfix) |
| { |
| size_t msg_len = 2048; |
| char *msg = cs_mem_calloc(sizeof(char), msg_len); |
| char *byte_seq = |
| byte_seq_to_str(test_input->bytes, test_input->bytes_count); |
| if (!msg) { |
| cs_mem_free(byte_seq); |
| return NULL; |
| } |
| char opt_seq[128] = { 0 }; |
| str_append_no_realloc(opt_seq, sizeof(opt_seq), "["); |
| for (size_t i = 0; i < test_input->options_count; ++i) { |
| str_append_no_realloc(opt_seq, sizeof(opt_seq), |
| test_input->options[i]); |
| if (i < test_input->options_count - 1) { |
| str_append_no_realloc(opt_seq, sizeof(opt_seq), ", "); |
| } |
| } |
| str_append_no_realloc(opt_seq, sizeof(opt_seq), "]"); |
| cs_snprintf(msg, msg_len, |
| "%sTestInput { arch: %s, options: %s, addr: 0x%" PRIx64 |
| ", bytes: %s }", |
| postfix, test_input->arch, opt_seq, test_input->address, |
| byte_seq); |
| cs_mem_free(byte_seq); |
| return msg; |
| } |
| |
| TestInsnData *test_insn_data_new() |
| { |
| TestInsnData *p = cs_mem_calloc(sizeof(TestInsnData), 1); |
| assert(p); |
| return p; |
| } |
| |
| void test_insn_data_free(TestInsnData *test_insn_data) |
| { |
| if (!test_insn_data) { |
| return; |
| } |
| cs_mem_free(test_insn_data->asm_text); |
| cs_mem_free(test_insn_data->op_str); |
| cs_mem_free(test_insn_data->mnemonic); |
| cs_mem_free(test_insn_data->id); |
| cs_mem_free(test_insn_data->alias_id); |
| test_detail_free(test_insn_data->details); |
| cs_mem_free(test_insn_data); |
| } |
| |
| TestInsnData *test_insn_data_clone(TestInsnData *test_insn_data) |
| { |
| assert(test_insn_data); |
| TestInsnData *tid = test_insn_data_new(); |
| tid->alias_id = test_insn_data->alias_id ? |
| cs_strdup(test_insn_data->alias_id) : |
| NULL; |
| tid->id = test_insn_data->id ? cs_strdup(test_insn_data->id) : NULL; |
| tid->is_alias = test_insn_data->is_alias; |
| tid->illegal = test_insn_data->illegal; |
| tid->size = test_insn_data->size; |
| tid->mnemonic = test_insn_data->mnemonic ? |
| cs_strdup(test_insn_data->mnemonic) : |
| NULL; |
| tid->op_str = test_insn_data->op_str ? |
| cs_strdup(test_insn_data->op_str) : |
| NULL; |
| tid->asm_text = test_insn_data->asm_text ? |
| cs_strdup(test_insn_data->asm_text) : |
| NULL; |
| if (test_insn_data->details) { |
| tid->details = test_detail_clone(test_insn_data->details); |
| } |
| return tid; |
| } |
| |
| TestExpected *test_expected_new() |
| { |
| TestExpected *p = cs_mem_calloc(sizeof(TestExpected), 1); |
| assert(p); |
| return p; |
| } |
| |
| void test_expected_free(TestExpected *test_expected) |
| { |
| if (!test_expected) { |
| return; |
| } |
| for (size_t i = 0; i < test_expected->insns_count; i++) { |
| test_insn_data_free(test_expected->insns[i]); |
| } |
| cs_mem_free(test_expected->insns); |
| cs_mem_free(test_expected); |
| } |
| |
| TestExpected *test_expected_clone(TestExpected *test_expected) |
| { |
| assert(test_expected); |
| TestExpected *te = test_expected_new(); |
| te->insns = cs_mem_calloc(sizeof(TestInsnData *), |
| test_expected->insns_count); |
| for (size_t i = 0; i < test_expected->insns_count; i++) { |
| te->insns[i] = test_insn_data_clone(test_expected->insns[i]); |
| te->insns_count++; |
| } |
| return te; |
| } |
| |
| /// Compares the given @asm_text to the @expected one. |
| /// Because Capstone sometimes deviates from the LLVM syntax |
| /// the strings don't need to be the same to be considered a valid match. |
| /// E.g. Capstone sometimes prints decimal numbers instead of hexadecimal |
| /// for readability. |
| static bool compare_asm_text(const char *asm_text, const char *expected, |
| size_t arch_bits) |
| { |
| if (!asm_text || !expected) { |
| fprintf(stderr, "[!] asm_text or expected was NULL\n"); |
| return false; |
| } |
| if (strcmp(asm_text, expected) == 0) { |
| return true; |
| } |
| // Normalize both strings |
| char asm_copy[MAX_ASM_TXT_MEM] = { 0 }; |
| strncpy(asm_copy, asm_text, MAX_ASM_TXT_MEM - 1); |
| trim_str(asm_copy); |
| replace_hex(asm_copy, sizeof(asm_copy)); |
| replace_negative(asm_copy, sizeof(asm_copy), arch_bits); |
| norm_spaces(asm_copy); |
| str_to_lower(asm_copy); |
| |
| char expected_copy[MAX_ASM_TXT_MEM] = { 0 }; |
| strncpy(expected_copy, expected, MAX_ASM_TXT_MEM - 1); |
| trim_str(expected_copy); |
| replace_hex(expected_copy, sizeof(expected_copy)); |
| replace_negative(expected_copy, sizeof(expected_copy), arch_bits); |
| norm_spaces(expected_copy); |
| str_to_lower(expected_copy); |
| |
| if (strcmp(asm_copy, expected_copy) == 0) { |
| return true; |
| } |
| |
| fprintf(stderr, |
| "Normalized asm-text doesn't match:\n" |
| "decoded: '%s'\n" |
| "expected: '%s'\n", |
| asm_copy, expected_copy); |
| return false; |
| } |
| |
| static bool ids_match(uint32_t actual, const char *expected) |
| { |
| compare_enum_ret(actual, expected, false); |
| return true; |
| } |
| |
| /// Compares the decoded instructions @insns against the @expected values and returns the result. |
| void test_expected_compare(csh *handle, TestExpected *expected, cs_insn *insns, |
| size_t insns_count, size_t arch_bits) |
| { |
| assert_int_equal(insns_count, expected->insns_count); |
| for (size_t i = 0; i < insns_count; ++i) { |
| TestInsnData *expec_data = expected->insns[i]; |
| // Test mandatory fields first |
| // The asm text is saved differently for different architectures. |
| // Either all in op_str or split in mnemonic and op_str |
| char asm_text[256] = { 0 }; |
| if (insns[i].mnemonic[0] != '\0') { |
| str_append_no_realloc(asm_text, sizeof(asm_text), |
| insns[i].mnemonic); |
| str_append_no_realloc(asm_text, sizeof(asm_text), " "); |
| } |
| if (insns[i].op_str[0] != '\0') { |
| str_append_no_realloc(asm_text, sizeof(asm_text), |
| insns[i].op_str); |
| } |
| if (!compare_asm_text(asm_text, expec_data->asm_text, |
| arch_bits)) { |
| fail_msg("asm-text mismatch\n"); |
| } |
| |
| // Not mandatory fields. If not initialized they should still match. |
| if (expec_data->id) { |
| assert_true(ids_match((uint32_t)insns[i].id, |
| expec_data->id)); |
| } |
| if (expec_data->is_alias != 0) { |
| if (expec_data->is_alias > 0) { |
| assert_true(insns[i].is_alias); |
| } else { |
| assert_false(insns[i].is_alias); |
| } |
| } |
| if (expec_data->illegal != 0) { |
| if (expec_data->illegal > 0) { |
| assert_true(insns[i].illegal); |
| } else { |
| assert_false(insns[i].illegal); |
| } |
| } |
| if (expec_data->size) { |
| assert_int_equal(insns[i].size, expec_data->size); |
| } |
| if (expec_data->alias_id) { |
| assert_true(ids_match((uint32_t)insns[i].alias_id, |
| expec_data->alias_id)); |
| } |
| if (expec_data->mnemonic) { |
| assert_string_equal(insns[i].mnemonic, |
| expec_data->mnemonic); |
| } |
| if (expec_data->op_str) { |
| assert_string_equal(insns[i].op_str, |
| expec_data->op_str); |
| } |
| if (expec_data->details) { |
| if (!insns[i].detail) { |
| fprintf(stderr, "detail is NULL\n"); |
| assert_non_null(insns[i].detail); |
| } |
| assert_true(test_expected_detail(handle, &insns[i], |
| expec_data->details)); |
| } |
| } |
| } |
| |
| TestCase *test_case_new() |
| { |
| TestCase *p = cs_mem_calloc(sizeof(TestCase), 1); |
| assert(p); |
| return p; |
| } |
| |
| void test_case_free(TestCase *test_case) |
| { |
| if (!test_case) { |
| return; |
| } |
| test_input_free(test_case->input); |
| test_expected_free(test_case->expected); |
| cs_mem_free(test_case->skip_reason); |
| cs_mem_free(test_case); |
| } |
| |
| TestCase *test_case_clone(TestCase *test_case) |
| { |
| assert(test_case); |
| TestCase *tc = test_case_new(); |
| TestInput *ti = test_input_clone(test_case->input); |
| tc->input = ti; |
| TestExpected *te = test_expected_clone(test_case->expected); |
| tc->expected = te; |
| tc->skip = test_case->skip; |
| if (tc->skip) { |
| tc->skip_reason = strdup(test_case->skip_reason); |
| } |
| return tc; |
| } |
| |
| TestFile *test_file_new() |
| { |
| TestFile *p = cs_mem_calloc(sizeof(TestFile), 1); |
| assert(p); |
| return p; |
| } |
| |
| void test_file_free(TestFile *test_file) |
| { |
| if (!test_file) { |
| return; |
| } |
| |
| for (size_t i = 0; i < test_file->test_cases_count; ++i) { |
| test_case_free(test_file->test_cases[i]); |
| } |
| |
| cs_mem_free(test_file->test_cases); |
| cs_mem_free(test_file->filename); |
| test_file->filename = NULL; |
| cs_mem_free(test_file); |
| } |
| |
| TestFile *test_file_clone(TestFile *test_file) |
| { |
| assert(test_file); |
| TestFile *tf = test_file_new(); |
| tf->filename = test_file->filename ? strdup(test_file->filename) : NULL; |
| tf->test_cases = |
| cs_mem_calloc(sizeof(TestCase *), test_file->test_cases_count); |
| |
| for (size_t i = 0; i < test_file->test_cases_count; |
| i++, tf->test_cases_count++) { |
| TestCase *tc = test_case_clone(test_file->test_cases[i]); |
| tf->test_cases[i] = tc; |
| } |
| return tf; |
| } |