#include <r_util.h>
#include "minunit.h"
#include <r_bin.h>
#include <r_bin_dwarf.h>

#define MODE 2

// Global test context to prevent leaks on early returns
static RBin *bin = NULL;
static RIO *io = NULL;

static bool setup(void) {
	bin = r_bin_new ();
	io = r_io_new ();
	if (!bin || !io) {
		r_bin_free (bin);
		r_io_free (io);
		return false;
	}
	r_io_bind (io, &bin->iob);
	return true;
}

static bool teardown(void) {
	r_bin_free (bin);
	r_io_free (io);
	bin = NULL;
	io = NULL;
	return true;
}

// the attribute accessors read the unit the test has named cu
#define GET_ATTR(die, idx) (r_bin_dwarf_die_attr (cu, &(die), idx))

// Improved check macros with proper vector access and null checks
#define check_attr_string(die, attr_idx, expect_string) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_streq(attr->string.content, expect_string, "Wrong string attribute information"); \
} while (0)

#define check_attr_name(die, attr_idx, expect_name) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->attr_name, expect_name, "Wrong attribute name"); \
} while (0)

#define check_attr_address(die, attr_idx, expect_addr) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->address, expect_addr, "Wrong attribute address"); \
} while (0)

#define check_attr_form(die, attr_idx, expect_form) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->attr_form, expect_form, "Wrong attribute form"); \
} while (0)

#define check_attr_data(die, attr_idx, expect_data) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->uconstant, expect_data, "Wrong attribute data"); \
} while (0)

#define check_attr_block_length(die, attr_idx, expect_len) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->block.length, expect_len, "Wrong attribute block length"); \
} while (0)

#define check_attr_block_data(die, attr_idx, data_idx, expect_data) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_notnull(attr->block.data, "Block data is NULL"); \
	mu_assert_eq(attr->block.data[data_idx], expect_data, "Wrong attribute block data"); \
} while (0)

#define check_attr_reference(die, attr_idx, expect_ref) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->reference, expect_ref, "Wrong attribute reference"); \
} while (0)

#define check_attr_flag(die, attr_idx, expect_flag) do { \
	RBinDwarfAttrValue *attr = GET_ATTR(die, attr_idx); \
	mu_assert_notnull(attr, "Attribute value is NULL"); \
	mu_assert_eq(attr->flag, expect_flag, "Wrong attribute flag"); \
} while (0)

#define check_die_abbr_code(die, expect_code) do { \
	mu_assert_eq(die.abbrev_code, expect_code, "Wrong abbrev code"); \
} while (0)

#define check_die_length(die, len) do { \
	mu_assert_eq(die.attr_count, len, "Wrong DIE attribute count"); \
} while (0)

#define check_die_tag(die, tg) do { \
	mu_assert_eq(die.tag, tg, "Wrong DIE tag"); \
} while (0)

#define check_basic_unit_header(vers, len, is64bit, addr_size, abbr_offset)                              \
	do {                                                                                             \
		mu_assert_eq (hdr.version, vers, "Wrong header version information");                    \
		mu_assert_eq (hdr.length, len, "Wrong header length information");                       \
		mu_assert_eq (hdr.is_64bit, is64bit, "Wrong header is_64bit information");               \
		mu_assert_eq (hdr.address_size, addr_size, "Wrong header address_size information");     \
		mu_assert_eq (hdr.abbrev_offset, abbr_offset, "Wrong header abbrev_offset information"); \
	} while (0)

bool test_dwarf3_c(void) {

	RBinFileOptions opt = {0};
	bool res = r_bin_open (bin, "bins/elf/dwarf3_c.elf", &opt);
	mu_assert ("dwarf3_c.elf binary could not be opened", res);

	RBinFile *bf = r_bin_cur (bin);
	mu_assert_notnull (bf, "Failed to get current bin file");
	RVecDwarfAbbrevDecl *da = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (da, "Failed to parse abbreviations");
	mu_assert_eq (RVecDwarfAbbrevDecl_length(da), 7, "Incorrect number of abbreviations");

	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, da);
	mu_assert_notnull (info, "Failed to parse debug info");
	mu_assert_notnull (info->comp_units, "Compilation units vector is NULL");
	mu_assert_eq (RVecDwarfCompUnit_length(info->comp_units), 1, "Incorrect number of info compilation units");

	// check header
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at(info->comp_units, 0);
	mu_assert_notnull (cu, "Compilation unit is NULL");
	RBinDwarfCompUnitHdr hdr = cu->hdr;

	check_basic_unit_header (3, 0xa9, false, 8, 0x0);

	mu_assert_notnull (cu->dies, "DIEs vector is NULL");
	mu_assert_eq (RVecDwarfDie_length(cu->dies), 11, "Wrong number of DIEs");
	mu_assert_eq (cu->offset, 0x0, "Wrong compilation unit offset");

	// check some of the attributes
	RBinDwarfDie *dies = cu->dies->_start;
	check_die_abbr_code (dies[0], 1);

	check_die_length (dies[0], 7);
	check_die_tag (dies[0], DW_TAG_compile_unit);

	check_attr_name (dies[0], 0, DW_AT_producer);
	check_attr_string (dies[0], 2, "main.c");
	mu_assert_eq (dies[0].attr_idx, 0, "Root attributes start the unit's attribute run");
	mu_assert_eq (dies[1].attr_idx, 7, "The next die's attributes follow the root's");
	mu_assert_null (r_bin_dwarf_die_attr (cu, &dies[0], 7), "No attribute past the die's count");
	check_die_abbr_code (dies[1], 2);
	check_die_abbr_code (dies[2], 3);
	check_die_abbr_code (dies[3], 4);
	check_die_abbr_code (dies[4], 5);
	check_die_abbr_code (dies[5], 0);
	check_die_abbr_code (dies[6], 6);
	check_die_abbr_code (dies[7], 7);

	check_attr_string (dies[7], 0, "b");
	check_attr_data (dies[7], 3, 15);

	check_die_abbr_code (dies[8], 7);
	check_die_abbr_code (dies[9], 0);
	check_die_abbr_code (dies[10], 0);

	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (da);
	mu_end;
}

bool test_dwarf4_cpp_multiple_modules(void) {

	RBinFileOptions opt = {0};
	bool res = r_bin_open (bin, "bins/elf/dwarf4_many_comp_units.elf", &opt);
	mu_assert ("dwarf4_many_comp_units.elf binary could not be opened", res);

	RBinFile *bf = r_bin_cur (bin);
	mu_assert_notnull (bf, "Failed to get current bin file");
	RVecDwarfAbbrevDecl *da = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (da, "Failed to parse abbreviations");
	mu_assert_eq (RVecDwarfAbbrevDecl_length(da), 37, "Incorrect number of abbreviations");

	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, da);
	mu_assert_notnull (info, "Failed parsing of debug_info");
	mu_assert_notnull (info->comp_units, "Compilation units vector is NULL");
	mu_assert_eq (RVecDwarfCompUnit_length(info->comp_units), 2, "Incorrect number of info compilation units");

	// check header
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at(info->comp_units, 0);
	mu_assert_notnull (cu, "Compilation unit is NULL");
	RBinDwarfCompUnitHdr hdr = cu->hdr;
	check_basic_unit_header (4, 0x2c0, false, 8, 0x0);

	// check some of the attributes
	mu_assert_notnull (cu->dies, "DIEs vector is NULL");
	mu_assert_eq (RVecDwarfDie_length(cu->dies), 73, "Wrong number of DIEs");
	mu_assert_eq (cu->offset, 0x0, "Wrong compilation unit offset");

	RBinDwarfDie *dies = cu->dies->_start;
	check_die_abbr_code (dies[0], 1);
	check_die_length (dies[0], 7);
	check_die_tag (dies[0], DW_TAG_compile_unit);

	check_attr_name (dies[0], 0, DW_AT_producer);
	check_attr_string (dies[0], 2, "../main.cpp");
	check_attr_name (dies[0], 6, DW_AT_ranges);
	check_attr_reference (dies[0], 6, 0x0);

	check_die_abbr_code (dies[1], 2);
	check_die_abbr_code (dies[2], 3);
	check_die_abbr_code (dies[3], 3);
	check_die_abbr_code (dies[4], 3);
	check_die_abbr_code (dies[5], 0);

	check_die_abbr_code (dies[6], 4);
	check_attr_reference (dies[6], 0, 0x6e);
	check_attr_data (dies[6], 1, 4);
	check_attr_string (dies[6], 2, "Bird");
	check_attr_data (dies[6], 3, 8);
	check_attr_data (dies[6], 4, 1);
	check_attr_data (dies[6], 5, 9);

	check_die_abbr_code (dies[7], 5);
	check_die_tag (dies[7], DW_TAG_member);
	check_attr_string (dies[7], 0, "_vptr$Bird");
	check_attr_reference (dies[7], 1, 0xc5);
	check_attr_data (dies[7], 2, 0);
	check_attr_flag (dies[7], 3, true);

	check_die_abbr_code (dies[8], 6);
	check_die_abbr_code (dies[9], 7);
	check_die_abbr_code (dies[10], 0);
	check_die_abbr_code (dies[11], 8);
	check_die_abbr_code (dies[12], 7);
	check_die_abbr_code (dies[13], 0);
	check_die_abbr_code (dies[14], 9);
	check_die_tag (dies[14], DW_TAG_subprogram);
	check_die_length (dies[14], 10);
	check_attr_string (dies[14], 0, "_ZN4Bird3flyEv");
	check_attr_string (dies[14], 1, "fly");
	check_attr_data (dies[14], 2, 1);
	check_attr_data (dies[14], 3, 12);
	check_attr_reference (dies[14], 4, 0xd8);
	check_attr_name (dies[14], 6, DW_AT_vtable_elem_location);
	check_attr_form (dies[14], 7, DW_FORM_flag_present);
	check_attr_flag (dies[14], 7, true);
	check_attr_name (dies[14], 8, DW_AT_external);
	check_attr_flag (dies[14], 8, true);
	check_attr_name (dies[14], 9, DW_AT_containing_type);
	check_attr_reference (dies[14], 9, 0x6e);
	check_die_abbr_code (dies[16], 0);
	check_die_abbr_code (dies[17], 0);
	check_die_abbr_code (dies[18], 10);
	check_die_abbr_code (dies[19], 11);
	check_die_abbr_code (dies[20], 12);
	check_die_abbr_code (dies[21], 13);
	check_die_tag (dies[21], DW_TAG_base_type);
	check_die_length (dies[21], 3);
	check_die_abbr_code (dies[22], 10);
	check_die_abbr_code (dies[23], 14);
	check_die_abbr_code (dies[24], 15);
	check_die_abbr_code (dies[25], 0);
	check_die_abbr_code (dies[26], 4);

	check_die_abbr_code (dies[66], 18);
	check_die_length (dies[66], 5);
	check_attr_reference (dies[66], 3, 0x2a7);

	check_die_abbr_code (dies[67], 15);
	check_die_length (dies[67], 4);
	check_attr_block_length (dies[67], 0, 2);
	check_attr_block_data (dies[67], 0, 0, 0x91);
	check_attr_block_data (dies[67], 0, 1, 0x78);
	check_attr_string (dies[67], 1, "this");
	check_attr_reference (dies[67], 2, 0x2be);
	check_attr_flag (dies[67], 3, true);

	check_die_abbr_code (dies[68], 0);
	check_die_abbr_code (dies[69], 10);
	check_die_abbr_code (dies[70], 10);
	check_die_tag (dies[70], DW_TAG_pointer_type);

	check_die_abbr_code (dies[71], 10);

	check_die_abbr_code (dies[72], 0);

	cu = RVecDwarfCompUnit_at(info->comp_units, 1);
	hdr = cu->hdr;
	check_basic_unit_header (4, 0x192, false, 8, 0xfd);

	// check some of the attributes
	mu_assert_eq (RVecDwarfDie_length(cu->dies), 42, "Wrong attribute information");
	mu_assert_eq (cu->offset, 0x2c4, "Wrong attribute information");

  dies = cu->dies->_start;

	check_die_abbr_code (dies[0], 1);
	check_die_length (dies[0], 7);
	check_die_tag (dies[0], DW_TAG_compile_unit);
	check_attr_name (dies[0], 0, DW_AT_producer);
	check_attr_string (dies[0], 0, "clang version 10.0.0-4ubuntu1 ");
	check_attr_data (dies[0], 1, 33);
	check_attr_string (dies[0], 2, "../mammal.cpp");
	check_attr_address (dies[0], 5, 0x0);
	check_attr_form (dies[0], 5, DW_FORM_addr);
	check_attr_name (dies[0], 6, DW_AT_ranges);
	check_attr_reference (dies[0], 6, 0xb0);
	check_die_abbr_code (dies[1], 2);
	check_die_abbr_code (dies[2], 3);
	check_die_abbr_code (dies[3], 4);
	check_die_abbr_code (dies[4], 5);
	check_die_abbr_code (dies[5], 0);
	check_die_abbr_code (dies[6], 6);
	check_die_abbr_code (dies[7], 5);
	check_die_abbr_code (dies[8], 0);

	check_die_abbr_code (dies[35], 8);
	check_die_tag (dies[35], DW_TAG_pointer_type);
	check_die_length (dies[35], 1);
	check_attr_form (dies[35], 0, DW_FORM_ref4);
	check_attr_reference (dies[35], 0, 0x407);

	check_die_abbr_code (dies[36], 19);
	check_die_tag (dies[36], DW_TAG_subprogram);
	check_die_length (dies[36], 5);
	check_attr_name (dies[36], 2, DW_AT_frame_base);
	check_attr_block_length (dies[36], 2, 1);
	check_attr_block_data (dies[36], 2, 0, 0x56);
	check_attr_reference (dies[36], 3, 0x442);
	check_attr_reference (dies[36], 4, 0x410);

	check_die_abbr_code (dies[40], 8);
	check_die_abbr_code (dies[41], 0);

	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (da);
	mu_end;
}

bool test_dwarf2_big_endian(void) {

	RBinFileOptions opt = {0};
	r_bin_file_options_init (&opt, -1, UT64_MAX, 0, 0);
	bool res = r_bin_open (bin, "bins/elf/ppc64_sudoku_dwarf", &opt);
	mu_assert ("dwarf4_many_comp_units.elf binary could not be opened", res);

	RBinFile *bf = r_bin_cur (bin);
	mu_assert_notnull (bf, "Failed to get current bin file");
	RVecDwarfAbbrevDecl *da = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_eq (RVecDwarfAbbrevDecl_length(da), 108, "Incorrect number of abbreviation");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, da);
	mu_assert_notnull (info, "Failed parsing of debug_info");
	mu_assert_eq (RVecDwarfCompUnit_length(info->comp_units), 1, "Incorrect number of info compilation units");

	// check header
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at(info->comp_units, 0);
	RBinDwarfCompUnitHdr hdr = cu->hdr;
	check_basic_unit_header (2, 0x38b9, false, 8, 0x0);

  RBinDwarfDie *dies = cu->dies->_start;
	check_die_abbr_code (dies[0], 1);
	check_die_length (dies[0], 7);
	check_die_tag (dies[0], DW_TAG_compile_unit);

	check_attr_name (dies[0], 0, DW_AT_producer);
	check_attr_string (dies[0], 0, "GNU C++14 9.3.0 -msecure-plt -mabi=elfv2 -mcpu=970 -gdwarf-2 -gstrict-dwarf -O1");
	check_attr_name (dies[0], 1, DW_AT_language);
	check_attr_data (dies[0], 1, DW_LANG_C_plus_plus);

	check_attr_name (dies[0], 4, DW_AT_low_pc);
	check_attr_reference (dies[0], 4, 0x0000000010000ec4);
	check_attr_name (dies[0], 5, DW_AT_high_pc);
	check_attr_reference (dies[0], 5, 0x0000000010001c48);
	check_attr_name (dies[0], 6, DW_AT_stmt_list);
	check_attr_reference (dies[0], 6, 0x0);

	check_die_abbr_code (dies[2], 3);
	check_die_tag (dies[2], DW_TAG_base_type);

	check_attr_name (dies[2], 0, DW_AT_byte_size);
	check_attr_data (dies[2], 0, 0x08);

	check_attr_name (dies[2], 1, DW_AT_encoding);
	check_attr_data (dies[2], 1, DW_ATE_unsigned);

	check_attr_name (dies[2], 2, DW_AT_name);
	check_attr_string (dies[2], 2, "long unsigned int");

  check_die_abbr_code (dies[3], 4);
  check_die_abbr_code (dies[4], 2);
  check_die_abbr_code (dies[5], 3);
  check_die_abbr_code (dies[6], 2);

	check_die_abbr_code (dies[7], 5);
	check_die_tag (dies[7], DW_TAG_structure_type);

	check_attr_name (dies[7], 0, DW_AT_name);
	check_attr_string (dies[7], 0, "_IO_FILE");

	check_attr_name (dies[7], 1, DW_AT_byte_size);
	check_attr_data (dies[7], 1, 0x01);

	check_attr_name (dies[7], 2, DW_AT_decl_file);
	check_attr_name (dies[7], 3, DW_AT_decl_line);
	check_attr_name (dies[7], 4, DW_AT_decl_column);
	check_attr_name (dies[7], 5, DW_AT_sibling);

	check_die_abbr_code (dies[1664], 108);
	check_die_tag (dies[1664], DW_TAG_subprogram);

	check_attr_name (dies[1664], 0, DW_AT_abstract_origin);
	check_attr_reference (dies[1664], 0, 0x2f32);

	check_attr_name (dies[1664], 1, DW_AT_MIPS_linkage_name);
	check_attr_string (dies[1664], 1, "_Z8isnumberc");

	check_attr_name (dies[1664], 2, DW_AT_low_pc);
	check_attr_reference (dies[1664], 2, 0x0000000010001aa4);

	check_attr_name (dies[1664], 3, DW_AT_high_pc);
	check_attr_reference (dies[1664], 3, 0x0000000010001ac8);

	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (da);
	mu_end;
}

static RBinSection *dwarf_test_find_section(RBinFile *bf, const char *name) {
	RVecRBinSection *sections = r_bin_file_get_sections_vec (bf);
	RBinSection *section;
	R_VEC_FOREACH (sections, section) {
		if (section->name && !strcmp (section->name, name)) {
			return section;
		}
	}
	return NULL;
}

static bool test_dwarf_empty_abbrev(void) {
	RBinFileOptions opt = { 0 };
	mu_assert ("Could not open DWARF fixture", r_bin_open (bin, "bins/elf/dwarf3_c.elf", &opt));
	RBinFile *bf = r_bin_cur (bin);
	RBinSection *section = dwarf_test_find_section (bf, ".debug_abbrev");
	mu_assert_notnull (section, "Missing .debug_abbrev section");
	static const ut8 empty_abbrev[] = { 0, 0, 1 };
	section->bytes = r_slice (empty_abbrev, sizeof (empty_abbrev));
	section->size = sizeof (empty_abbrev);
	RVecDwarfAbbrevDecl *decls = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (decls, "Failed to parse empty abbreviations");
	mu_assert ("Abbreviations must be empty", RVecDwarfAbbrevDecl_empty (decls));
	char *printed = r_bin_dwarf_print_info (bf, decls);
	mu_assert_streq (printed, "", "No debug info should be printed");
	free (printed);
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, decls);
	mu_assert_notnull (info, "Failed to parse debug info");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 0, "Unit without abbreviations must be skipped");
	r_bin_dwarf_free_debug_info (info);
	mu_assert ("Failed to parse compilation directories", r_bin_dwarf_parse_comp_dirs (bf, decls));
	RList *files = r_bin_dwarf_parse_comp_unit_files (bf, decls);
	mu_assert_notnull (files, "Failed to parse source files");
	mu_assert_eq (r_list_length (files), 0, "No source files without abbreviations");
	r_list_free (files);
	RVecDwarfAbbrevDecl_free (decls);
	mu_end;
}

static RBuffer *dwarf_test_clone_info_patch(const char *path, ut64 info_offset, const ut8 *patch, size_t patch_size) {
	RBinFileOptions opt = { 0 };
	if (!r_bin_open (bin, path, &opt)) {
		return NULL;
	}
	RBinFile *bf = r_bin_cur (bin);
	RBinSection *section = dwarf_test_find_section (bf, ".debug_info");
	ut64 file_size = r_buf_size (bf->buf);
	if (!section || info_offset > section->size
		|| patch_size > section->size - info_offset
		|| section->paddr > file_size
		|| section->size > file_size - section->paddr
		|| file_size > SIZE_MAX) {
		return NULL;
	}
	ut8 *bytes = malloc ((size_t)file_size);
	if (!bytes || r_buf_read_at (bf->buf, 0, bytes, file_size) != file_size) {
		free (bytes);
		return NULL;
	}
	memcpy (bytes + section->paddr + info_offset, patch, patch_size);
	RBuffer *result = r_buf_new_with_bytes (bytes, (size_t)file_size);
	free (bytes);
	return result;
}

bool test_dwarf_bad_strp_is_local(void) {
	const ut8 bad_offset[] = { 0xff, 0xff, 0xff, 0xff };
	RBuffer *buf = dwarf_test_clone_info_patch (
		"bins/elf/dwarf4_many_comp_units.elf", 0xc,
		bad_offset, sizeof (bad_offset));
	mu_assert_notnull (buf, "Couldn't clone multi-CU DWARF fixture");
	RBinFileOptions opt = { 0 };
	r_bin_file_options_init (&opt, -1, UT64_MAX, 0, 0);
	opt.filename = "bad-strp.elf";
	mu_assert_true (r_bin_open_buf (bin, buf, &opt),
		"Couldn't open bad-strp fixture");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse bad-strp abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "A bad string offset must not reject debug_info");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 2,
		"A bad string offset must not discard either CU");
	RBinDwarfCompUnit *first = RVecDwarfCompUnit_at (info->comp_units, 0);
	RBinDwarfDie *root = first && first->dies? RVecDwarfDie_at (first->dies, 0): NULL;
	RBinDwarfAttrValue *producer = root? r_bin_dwarf_die_attr (first, root, 0): NULL;
	mu_assert_notnull (producer, "Missing bad-strp producer attribute");
	mu_assert_eq (producer->string.offset, UT32_MAX,
		"Bad string offset was not consumed");
	mu_assert_null (producer->string.content,
		"Out-of-range string offset unexpectedly resolved");
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	r_unref (buf);
	mu_end;
}

bool test_dwarf_bad_cu_is_skipped(void) {
	const ut8 bad_abbrev = 0x7f;
	RBuffer *buf = dwarf_test_clone_info_patch (
		"bins/elf/dwarf4_many_comp_units.elf", 0xb,
		&bad_abbrev, sizeof (bad_abbrev));
	mu_assert_notnull (buf, "Couldn't clone multi-CU DWARF fixture");
	RBinFileOptions opt = { 0 };
	r_bin_file_options_init (&opt, -1, UT64_MAX, 0, 0);
	opt.filename = "bad-first-cu.elf";
	mu_assert_true (r_bin_open_buf (bin, buf, &opt),
		"Couldn't open malformed-CU fixture");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse malformed-CU abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "A malformed CU must not reject debug_info");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 1,
		"Only the malformed CU should be skipped");
	RBinDwarfCompUnit *unit = RVecDwarfCompUnit_at (info->comp_units, 0);
	mu_assert_notnull (unit, "Surviving CU is NULL");
	mu_assert_eq (unit->offset, 0x2c4,
		"Parser did not resume at the next CU boundary");
	RList *files = r_bin_dwarf_parse_comp_unit_files (bf, abbrevs);
	mu_assert_notnull (files, "Root-only CU walk failed after malformed CU");
	mu_assert ("Surviving CU source files were lost", r_list_length (files) > 0);
	r_list_free (files);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	r_unref (buf);
	mu_end;
}

bool test_dwarf_bad_header_preserves_prior_cus(void) {
	const ut8 bad_length[] = { 0xfe, 0xff, 0xff, 0xff };
	RBuffer *buf = dwarf_test_clone_info_patch (
		"bins/elf/dwarf4_many_comp_units.elf", 0x2c4,
		bad_length, sizeof (bad_length));
	mu_assert_notnull (buf, "Couldn't clone malformed-header fixture");
	RBinFileOptions opt = { 0 };
	r_bin_file_options_init (&opt, -1, UT64_MAX, 0, 0);
	opt.filename = "bad-second-header.elf";
	mu_assert_true (r_bin_open_buf (bin, buf, &opt),
		"Couldn't open malformed-header fixture");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse malformed-header abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "A malformed later header discarded prior CUs");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 1,
		"The malformed later header was not isolated");
	RBinDwarfCompUnit *unit = RVecDwarfCompUnit_at (info->comp_units, 0);
	mu_assert_notnull (unit, "Preserved CU is NULL");
	mu_assert_eq (unit->offset, 0, "The wrong CU survived the malformed header");
	RList *files = r_bin_dwarf_parse_comp_unit_files (bf, abbrevs);
	mu_assert_notnull (files, "Root walk discarded files from the prior CU");
	mu_assert ("Prior CU source files were lost", r_list_length (files) > 0);
	r_list_free (files);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	r_unref (buf);
	mu_end;
}

bool test_dwarf_bad_address_size_is_local(void) {
	const ut8 bad_address_size = 0;
	RBuffer *buf = dwarf_test_clone_info_patch (
		"bins/elf/dwarf4_many_comp_units.elf", 0xa,
		&bad_address_size, sizeof (bad_address_size));
	mu_assert_notnull (buf, "Couldn't clone bad-address-size fixture");
	RBinFileOptions opt = { 0 };
	r_bin_file_options_init (&opt, -1, UT64_MAX, 0, 0);
	opt.filename = "bad-address-size.elf";
	mu_assert_true (r_bin_open_buf (bin, buf, &opt),
		"Couldn't open bad-address-size fixture");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse bad-address-size abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "An invalid address size rejected all debug_info");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 1,
		"The invalid address size was not isolated to its CU");
	RBinDwarfCompUnit *unit = RVecDwarfCompUnit_at (info->comp_units, 0);
	mu_assert_notnull (unit, "Surviving CU is NULL");
	mu_assert_eq (unit->offset, 0x2c4,
		"Parser did not resume after the invalid address size");
	RList *files = r_bin_dwarf_parse_comp_unit_files (bf, abbrevs);
	mu_assert_notnull (files, "Root walk failed after an invalid address size");
	mu_assert ("Surviving CU source files were lost", r_list_length (files) > 0);
	r_list_free (files);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	r_unref (buf);
	mu_end;
}

bool test_dwarf_explicit_zero_rebase(void) {
	ut8 low_pc[8] = { 0 };
	low_pc[0] = 1;
	RBuffer *buf = dwarf_test_clone_info_patch (
		"bins/elf/dwarf4_many_comp_units.elf", 0x2b,
		low_pc, sizeof (low_pc));
	mu_assert_notnull (buf, "Couldn't clone rebase fixture");
	RBinFileOptions opt = { 0 };
	r_bin_file_options_init (&opt, -1, 0, 0, 0);
	opt.filename = "zero-rebase.elf";
	mu_assert_true (r_bin_open_buf (bin, buf, &opt),
		"Couldn't open explicit-zero-rebase fixture");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse rebase abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "An underflowing address must not reject its CU");
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at (info->comp_units, 0);
	mu_assert_notnull (cu, "Rebased CU is NULL");
	RBinDwarfDie *root = RVecDwarfDie_at (cu->dies, 0);
	RBinDwarfDie *function = RVecDwarfDie_at (cu->dies, 1);
	mu_assert_notnull (root, "Rebased CU root is NULL");
	mu_assert_notnull (function, "Rebased function DIE is NULL");
	check_attr_address ((*root), 5, 0);
	check_attr_address ((*function), 0, 1);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	r_unref (buf);
	mu_end;
}

bool test_dwarf5_mixed_address_table_format(void) {
	RBinFileOptions opt = { 0 };
	mu_assert_true (r_bin_open (bin, "bins/elf/dwarf5_line_cl", &opt),
		"dwarf5_line_cl binary could not be opened");
	RBinFile *bf = r_bin_cur (bin);
	RBinSection *info_section = dwarf_test_find_section (bf, ".debug_info");
	RBinSection *addr_section = dwarf_test_find_section (bf, ".debug_addr");
	mu_assert_notnull (info_section, "Missing .debug_info section");
	mu_assert_notnull (addr_section, "Missing .debug_addr section");
	mu_assert_eq (info_section->size, 0xf1, "Unexpected debug_info fixture size");
	mu_assert_eq (addr_section->size, 0x40, "Unexpected debug_addr fixture size");
	RSlice info_bytes = r_arena_scalloc (bf->arena, info_section->size);
	RSlice addr_bytes = r_arena_scalloc (bf->arena, addr_section->size + 8);
	mu_assert_notnull (info_bytes.ptr, "Couldn't clone .debug_info");
	mu_assert_notnull (addr_bytes.ptr, "Couldn't create DWARF64 .debug_addr");
	mu_assert_eq (r_buf_read_at (bf->buf, info_section->paddr,
		(void *)info_bytes.ptr, info_section->size), info_section->size,
		"Couldn't read .debug_info");
	mu_assert_eq (r_read_le32 (info_bytes.ptr + 0x1f), 8,
		"Unexpected DW_AT_addr_base location");
	r_write_le32 ((ut8 *)info_bytes.ptr + 0x1f, 16);
	info_section->bytes = info_bytes;
	r_write_le32 ((ut8 *)addr_bytes.ptr, DWARF_INIT_LEN_64);
	r_write_le64 ((ut8 *)addr_bytes.ptr + 4, 0x3c);
	r_write_le16 ((ut8 *)addr_bytes.ptr + 12, 5);
	((ut8 *)addr_bytes.ptr)[14] = 8;
	mu_assert_eq (r_buf_read_at (bf->buf, addr_section->paddr + 8,
		(ut8 *)addr_bytes.ptr + 16, addr_section->size - 8),
		addr_section->size - 8, "Couldn't copy .debug_addr entries");
	addr_section->bytes = addr_bytes;
	addr_section->size += 8;
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Couldn't parse DWARF5 abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "DWARF64 address table failed in a DWARF32 CU");
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at (info->comp_units, 0);
	RBinDwarfDie *root = cu? RVecDwarfDie_at (cu->dies, 0): NULL;
	mu_assert_notnull (root, "DWARF5 root DIE is NULL");
	check_attr_address ((*root), 6, 0x1140);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	mu_end;
}

bool test_dwarf5_indexed_strings_and_addresses(void) {
	RBinFileOptions opt = { 0 };
	mu_assert_true (r_bin_open (bin, "bins/elf/dwarf5_line_cl", &opt),
		"dwarf5_line_cl binary could not be opened");
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *abbrevs = r_bin_dwarf_parse_abbrev (bf);
	mu_assert_notnull (abbrevs, "Failed parsing DWARF5 abbreviations");
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, abbrevs);
	mu_assert_notnull (info, "Failed parsing DWARF5 indexed forms");
	RBinDwarfCompUnit *cu = RVecDwarfCompUnit_at (info->comp_units, 0);
	RBinDwarfDie *root = cu? RVecDwarfDie_at (cu->dies, 0): NULL;
	mu_assert_notnull (root, "DWARF5 root DIE is NULL");
	check_attr_string ((*root), 0, "clang version 18.1.8");
	check_attr_string ((*root), 2, "dwarf-line.c");
	check_attr_string ((*root), 5, "/usr/w/g/radare2/test/bins/src");
	check_attr_address ((*root), 6, 0x1140);
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (abbrevs);
	mu_end;
}

bool test_dwarf_find_die(void) {
	RBinFileOptions opt = { 0 };
	bool res = r_bin_open (bin, "bins/elf/dwarf4_many_comp_units.elf", &opt);
	mu_assert ("dwarf4_many_comp_units.elf binary could not be opened", res);
	RBinFile *bf = r_bin_cur (bin);
	RVecDwarfAbbrevDecl *da = r_bin_dwarf_parse_abbrev (bf);
	RBinDwarfDebugInfo *info = r_bin_dwarf_parse_info (bf, da);
	mu_assert_notnull (info, "Failed parsing of debug_info");
	mu_assert_eq (RVecDwarfCompUnit_length (info->comp_units), 2, "Incorrect number of info compilation units");
	RBinDwarfCompUnit *first = RVecDwarfCompUnit_at (info->comp_units, 0);
	RBinDwarfCompUnit *second = RVecDwarfCompUnit_at (info->comp_units, 1);
	RBinDwarfDie *root = RVecDwarfDie_at (second->dies, 0);
	mu_assert_eq (root->offset, 0x2cf, "Second unit root offset");
	mu_assert_ptreq (r_bin_dwarf_find_die (info, 0x2cf, NULL), root, "Root of the second unit");
	RBinDwarfDie *nested = RVecDwarfDie_at (second->dies, 20);
	mu_assert_notnull (nested, "Nested die of the second unit is missing");
	RBinDwarfCompUnit *found = NULL;
	mu_assert_ptreq (r_bin_dwarf_find_die (info, nested->offset, &found), nested, "Nested die of the second unit");
	mu_assert_ptreq (found, second, "The unit owning the nested die");
	RBinDwarfDie *last = RVecDwarfDie_at (first->dies, 72);
	mu_assert_notnull (last, "Null die of the first unit is missing");
	mu_assert_eq (last->abbrev_code, 0, "Null die closes the first unit");
	mu_assert_ptreq (r_bin_dwarf_find_die (info, last->offset, NULL), last, "Null die of the first unit");
	mu_assert_ptreq (r_bin_dwarf_find_die (info, 0xb, NULL), RVecDwarfDie_at (first->dies, 0), "Root of the first unit");
	mu_assert_null (r_bin_dwarf_find_die (info, 0, NULL), "First unit header is not a die");
	mu_assert_null (r_bin_dwarf_find_die (info, 0x2c4, NULL), "Second unit header is not a die");
	RBinDwarfDie *after_root = RVecDwarfDie_at (second->dies, 1);
	mu_assert_notnull (after_root, "Second unit has a die after its root");
	mu_assert ("The root attributes leave no gap to probe", after_root->offset > 0x2d0);
	mu_assert_null (r_bin_dwarf_find_die (info, 0x2d0, NULL), "Offset inside a die is not a die");
	mu_assert_null (r_bin_dwarf_find_die (info, 0x2c4 + 4 + 0x192, NULL), "Offset past the last unit");
	r_bin_dwarf_free_debug_info (info);
	RVecDwarfAbbrevDecl_free (da);
	mu_end;
}

#define run_test_with_setup(test_func) do { \
	if (!setup()) { \
		printf("Setup failed for " #test_func "\n"); \
		return 1; \
	} \
	mu_run_test(test_func); \
	teardown(); \
} while (0)

bool all_tests(void) {
	run_test_with_setup(test_dwarf_empty_abbrev);
	run_test_with_setup(test_dwarf3_c);
	run_test_with_setup(test_dwarf4_cpp_multiple_modules);
	run_test_with_setup(test_dwarf2_big_endian);
	run_test_with_setup(test_dwarf_bad_strp_is_local);
	run_test_with_setup(test_dwarf_bad_cu_is_skipped);
	run_test_with_setup(test_dwarf_bad_header_preserves_prior_cus);
	run_test_with_setup(test_dwarf_bad_address_size_is_local);
	run_test_with_setup(test_dwarf_explicit_zero_rebase);
	run_test_with_setup(test_dwarf5_mixed_address_table_format);
	run_test_with_setup(test_dwarf5_indexed_strings_and_addresses);
	run_test_with_setup(test_dwarf_find_die);
	return tests_passed != tests_run;
}

int main(int argc, char **argv) {
	return all_tests();
}
