#include <r_anal.h>
#include <r_core.h>
#include "minunit.h"

#include "test_anal_block_invars.inl"
#define check_invariants block_check_invariants
#define check_leaks block_check_leaks

static void block_unref(RAnalBlock *block) {
	r_unref (block);
}

static size_t blocks_count(RAnal *anal) {
	size_t count = 0;
	RBIter iter;
	RAnalBlock *block;
	r_rbtree_foreach(anal->bb_tree, iter, block, RAnalBlock, _rb) {
		count++;
	}
	return count;
}


#define assert_invariants(anal) do { if (!check_invariants (anal)) { return false; } } while (0)
#define assert_leaks(anal) do { if (!check_leaks (anal)) { return false; } } while (0)

bool test_r_anal_block_create(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	mu_assert_eq (blocks_count (anal), 0, "initial count");

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);
	mu_assert ("created block", block);
	mu_assert_eq (block->addr, 0x1337, "created addr");
	mu_assert_eq (block->size, 42, "created size");
	mu_assert_eq (r_ref_count (block), 1, "created initial ref");
	mu_assert_eq (blocks_count (anal), 1, "count after create");

	RAnalBlock *block2 = r_anal_create_block (anal, 0x133f, 100);
	assert_invariants (anal);
	mu_assert ("created block (overlap)", block2);
	mu_assert_eq (block2->addr, 0x133f, "created addr");
	mu_assert_eq (block2->size, 100, "created size");
	mu_assert_eq (r_ref_count (block2), 1, "created initial ref");
	mu_assert_eq (blocks_count (anal), 2, "count after create");

	RAnalBlock *block3 = r_anal_create_block (anal, 0x1337, 5);
	assert_invariants (anal);
	mu_assert ("no double create on same start", !block3);
	mu_assert_eq (blocks_count (anal), 2, "count after failed create");

	r_unref (block);
	r_unref (block2);

	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_contains(void) {
	RAnalBlock dummy = {0};
	dummy.addr = 0x1337;
	dummy.size = 42;
	mu_assert ("contains before", !r_anal_block_contains (&dummy, 100));
	mu_assert ("contains start", r_anal_block_contains (&dummy, 0x1337));
	mu_assert ("contains inside", r_anal_block_contains (&dummy, 0x1339));
	mu_assert ("contains last", r_anal_block_contains (&dummy, 0x1337 + 42 - 1));
	mu_assert ("contains after", !r_anal_block_contains (&dummy, 0x1337 + 42));
	mu_end;
}

bool test_r_anal_block_split(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);
	mu_assert_eq (blocks_count (anal), 1, "count after create");
	block->jump = 0xdeadbeef;
	block->fail = 0xc0ffee;
	block->ninstr = 5;
	r_anal_bb_set_offset (block, 0, 0);
	r_anal_bb_set_offset (block, 1, 1);
	r_anal_bb_set_offset (block, 2, 2);
	r_anal_bb_set_offset (block, 3, 4);
	r_anal_bb_set_offset (block, 4, 30);

	RAnalBlock *second = r_anal_block_split (block, 0x1337);
	assert_invariants (anal);
	mu_assert_ptreq (second, block, "nop split on first addr");
	mu_assert_eq (blocks_count (anal), 1, "count after nop split");
	mu_assert_eq (r_ref_count (block), 2, "ref after nop split");
	r_unref (block);

	second = r_anal_block_split (block, 0x1339);
	assert_invariants (anal);
	mu_assert_ptrneq (second, block, "non-nop split");
	mu_assert_eq (blocks_count (anal), 2, "count after non-nop split");

	mu_assert_eq (block->addr, 0x1337, "first addr after split");
	mu_assert_eq (block->size, 2, "first size after split");
	mu_assert_eq (second->addr, 0x1339, "first addr after split");
	mu_assert_eq (second->size, 40, "first size after split");

	mu_assert_eq (block->jump, second->addr, "first jump");
	mu_assert_eq (block->fail, UT64_MAX, "first fail");
	mu_assert_eq (second->jump, 0xdeadbeef, "second jump");
	mu_assert_eq (second->fail, 0xc0ffee, "second fail");

	mu_assert_eq (block->ninstr, 2, "first ninstr after split");
	mu_assert_eq (r_anal_bb_offset_inst (block, 0), 0, "first op_pos[0]");
	mu_assert_eq (r_anal_bb_offset_inst (block, 1), 1, "first op_pos[1]");

	mu_assert_eq (second->ninstr, 3, "second ninstr after split");
	mu_assert_eq (r_anal_bb_offset_inst (second, 0), 0, "second op_pos[0]");
	mu_assert_eq (r_anal_bb_offset_inst (second, 1), 2, "second op_pos[1]");
	mu_assert_eq (r_anal_bb_offset_inst (second, 2), 28, "second op_pos[2]");

	r_unref (block);
	r_unref (second);

	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_split_in_function(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalFunction *fcn = r_anal_create_function (anal, "bbowner", 0x1337, 0, NULL);
	assert_invariants (anal);

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);
	mu_assert_eq (blocks_count (anal), 1, "count after create");
	r_anal_function_add_block (fcn, block);
	assert_invariants (anal);
	mu_assert_eq (r_ref_count (block), 2, "block refs after adding to function");

	RAnalBlock *second = r_anal_block_split (block, 0x1339);
	assert_invariants (anal);
	mu_assert_ptrneq (second, block, "non-nop split");
	mu_assert_eq (blocks_count (anal), 2, "count after non-nop split");
	mu_assert_eq (r_ref_count (block), 2, "first block refs after adding to function");
	mu_assert_eq (r_ref_count (second), 2, "second block refs after adding to function");

	mu_assert ("function has first block after split", r_list_contains (fcn->bbs, block));
	mu_assert ("function has second block after split", r_list_contains (fcn->bbs, second));
	mu_assert ("second block is in function after split", r_list_contains (block->fcns, fcn));
	mu_assert ("second block is in function after split", r_list_contains (second->fcns, fcn));

	r_unref (block);
	r_unref (second);

	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_merge(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalBlock *first = r_anal_create_block (anal, 0x1337, 42);
	RAnalBlock *second = r_anal_create_block (anal, 0x1337 + 42, 624);
	assert_invariants (anal);
	mu_assert_eq (blocks_count (anal), 2, "count after create");
	second->jump = 0xdeadbeef;
	second->fail = 0xc0ffee;

	first->ninstr = 3;
	r_anal_bb_set_offset (first, 0, 0);
	r_anal_bb_set_offset (first, 1, 13);
	r_anal_bb_set_offset (first, 2, 16);

	second->ninstr = 4;
	r_anal_bb_set_offset (second, 0, 0);
	r_anal_bb_set_offset (second, 1, 4);
	r_anal_bb_set_offset (second, 2, 9);
	r_anal_bb_set_offset (second, 3, 30);

	bool success = r_anal_block_merge (first, second);
	assert_invariants (anal);
	mu_assert ("merge success", success);
	mu_assert_eq (blocks_count (anal), 1, "count after merge");
	mu_assert_eq (first->addr, 0x1337, "addr after merge");
	mu_assert_eq (first->size, 666, "size after merge");
	mu_assert_eq (first->jump, 0xdeadbeef, "jump after merge");
	mu_assert_eq (first->fail, 0xc0ffee, "fail after merge");

	mu_assert_eq (first->ninstr, 3+4, "ninstr after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 0), 0, "offset 0 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 1), 13, "offset 1 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 2), 16, "offset 2 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 3), 42+0, "offset 3 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 4), 42+4, "offset 4 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 5), 42+9, "offset 5 after merge");
	mu_assert_eq (r_anal_bb_offset_inst (first, 6), 42+30, "offset 6 after merge");

	r_unref (first);
	// second must be already freed by the merge!

	assert_invariants (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_merge_in_function(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalFunction *fcn = r_anal_create_function (anal, "bbowner", 0x1337, 0, NULL);

	RAnalBlock *first = r_anal_create_block (anal, 0x1337, 42);
	RAnalBlock *second = r_anal_create_block (anal, 0x1337 + 42, 624);
	assert_invariants (anal);
	mu_assert_eq (blocks_count (anal), 2, "count after create");

	r_anal_function_add_block (fcn, first);
	assert_invariants (anal);
	r_anal_function_add_block (fcn, second);
	assert_invariants (anal);

	bool success = r_anal_block_merge (first, second);
	assert_invariants (anal);
	mu_assert ("merge success", success);
	mu_assert_eq (blocks_count (anal), 1, "count after merge");
	mu_assert_eq (r_list_length (fcn->bbs), 1, "fcn bbs after merge");
	mu_assert_eq (r_list_length (first->fcns), 1, "bb functions after merge");
	mu_assert ("function has merged block", r_list_contains (fcn->bbs, first));
	mu_assert ("merged block is in function", r_list_contains (first->fcns, fcn));

	r_unref (first);
	// second must be already freed by the merge!

	assert_invariants (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_delete(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalFunction *fcn = r_anal_create_function (anal, "bbowner", 0x1337, 0, NULL);

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);
	mu_assert_eq (blocks_count (anal), 1, "count after create");

	r_anal_function_add_block (fcn, block);
	assert_invariants (anal);
	mu_assert_eq (r_ref_count (block), 2, "refs after adding");
	mu_assert_eq (r_list_length (fcn->bbs), 1, "fcn bbs after add");
	mu_assert_eq (r_list_length (block->fcns), 1, "bb fcns after add");

	r_anal_delete_block (block);
	assert_invariants (anal);
	mu_assert_eq (r_ref_count (block), 1, "refs after delete");
	mu_assert_eq (r_list_length (fcn->bbs), 0, "fcn bbs after delete");
	mu_assert_eq (r_list_length (block->fcns), 0, "bb fcns after delete");

	r_unref (block);

	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_reset(void) {
	RAnal *anal = r_anal_new ();
	r_anal_block_reset (anal);
	RAnalFunction *fcn = r_anal_create_function (anal, "owner", 0x100, 0, NULL);
	RAnalFunction *shared_owner = r_anal_create_function (anal, "shared", 0x200, 0, NULL);
	RAnalFunction *unregistered = r_anal_function_new (anal);
	unregistered->name = strdup ("unregistered");
	RAnalBlock *first = r_anal_create_block (anal, 0x100, 0x10);
	RAnalBlock *second = r_anal_create_block (anal, 0x200, 0x20);
	r_anal_create_block (anal, 0x300, 0x10);
	r_anal_function_add_block (fcn, first);
	r_anal_function_add_block (shared_owner, first);
	r_anal_function_add_block (unregistered, second);
	r_anal_function_add_block (fcn, second);
	r_unref (first);
	r_unref (second);
	mu_assert_eq (r_anal_function_linear_size (fcn), 0x120, "cached range before reset");
	fcn->meta.numcallrefs = 3;
	assert_invariants (anal);

	r_anal_block_reset (anal);
	mu_assert_null (anal->bb_tree, "reset clears tree");
	mu_assert_eq (r_list_length (anal->fcns), 2, "reset keeps functions");
	mu_assert_eq (r_list_length (fcn->bbs), 0, "reset clears function blocks");
	mu_assert_eq (r_list_length (shared_owner->bbs), 0, "reset clears shared blocks");
	mu_assert_eq (r_list_length (unregistered->bbs), 0, "reset follows unregistered owners");
	mu_assert_eq (r_anal_function_linear_size (fcn), 0, "reset invalidates cached range");
	mu_assert_eq (fcn->meta.numcallrefs, -1, "reset invalidates cached call count");
	assert_invariants (anal);
	r_anal_function_free (unregistered);

	r_anal_block_reset (anal);
	RAnalBlock *replacement = r_anal_create_block (anal, 0x100, 0x30);
	r_anal_function_add_block (fcn, replacement);
	r_unref (replacement);
	mu_assert_eq (r_anal_function_linear_size (fcn), 0x30, "function reusable after reset");
	assert_invariants (anal);
	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_reset_refs(void) {
	RAnal *anal = r_anal_new ();
	RAnalFunction *fcn = r_anal_create_function (anal, "owner", 0x100, 0, NULL);
	RAnalFunction *shared_owner = r_anal_create_function (anal, "shared", 0x200, 0, NULL);
	RAnalBlock *block = r_anal_create_block (anal, 0x100, 0x10);
	r_anal_function_add_block (fcn, block);
	r_anal_function_add_block (shared_owner, block);
	RList *retained = r_anal_get_blocks_in (anal, 0x100);
	r_unref (block);
	block = r_list_first (retained);
	RAnalBlock *orphan = r_anal_create_block (anal, 0x300, 0x10);
	(void)r_ref (orphan);

	r_anal_block_reset (anal);
	mu_assert_eq (r_ref_count (block), 1, "reset preserves query reference");
	mu_assert_eq (r_list_length (block->fcns), 0, "retained block has no owners");
	mu_assert_null (block->anal, "retained block detached from analysis");
	mu_assert_eq (r_ref_count (orphan), 1, "reset releases orphan creation reference");
	mu_assert_null (orphan->anal, "retained orphan detached from analysis");

	RAnalBlock *replacement = r_anal_create_block (anal, 0x100, 0x20);
	r_anal_function_add_block (fcn, replacement);
	r_unref (replacement);
	r_list_free (retained);
	mu_assert_eq (blocks_count (anal), 1, "releasing old block keeps replacement");
	assert_invariants (anal);
	assert_leaks (anal);
	r_anal_free (anal);
	r_unref (orphan);
	mu_end;
}

bool test_r_anal_block_set_size(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalFunction *fcn = r_anal_create_function (anal, "bbowner", 0x1337, 0, NULL);

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);

	r_anal_function_add_block (fcn, block);
	assert_invariants (anal);

	r_anal_block_set_size (block, 300);
	assert_invariants (anal);
	mu_assert_eq (block->size, 300, "size after set_size");

	RAnalBlock *second = r_anal_create_block (anal, 0x1337+300, 100);
	assert_invariants (anal);
	r_anal_function_add_block (fcn, block);
	assert_invariants (anal);
	r_anal_function_linear_size (fcn); // trigger lazy calculation of min/max cache
	assert_invariants (anal);

	r_anal_block_set_size (second, 500);
	assert_invariants (anal);
	mu_assert_eq (second->size, 500, "size after set_size");

	r_anal_block_set_size (block, 80);
	assert_invariants (anal);
	mu_assert_eq (block->size, 80, "size after set_size");

	r_unref (block);
	r_unref (second);
	assert_invariants (anal);

	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_relocate(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalFunction *fcn = r_anal_create_function (anal, "bbowner", 0x1337, 0, NULL);

	RAnalBlock *block = r_anal_create_block (anal, 0x1337, 42);
	assert_invariants (anal);

	r_anal_function_add_block (fcn, block);
	assert_invariants (anal);
	r_anal_function_linear_size (fcn); // trigger lazy calculation of min/max cache
	assert_invariants (anal);

	bool success = r_anal_block_relocate (block, 0x200, 0x100);
	mu_assert ("relocate success", success);
	assert_invariants (anal);
	mu_assert_eq (block->addr, 0x200, "addr after relocate");
	mu_assert_eq (block->size, 0x100, "size after relocate");

	RAnalBlock *second = r_anal_create_block (anal, 0x1337+300, 100);
	assert_invariants (anal);
	r_anal_function_add_block (fcn, second);
	assert_invariants (anal);

	success = r_anal_block_relocate (second, 0x400, 0x123);
	mu_assert ("relocate success", success);
	assert_invariants (anal);
	mu_assert_eq (second->addr, 0x400, "addr after relocate");
	mu_assert_eq (second->size, 0x123, "size after relocate");
	r_anal_function_linear_size (fcn); // trigger lazy calculation of min/max cache
	assert_invariants (anal);

	success = r_anal_block_relocate (block, 0x400, 0x333);
	mu_assert ("relocate fail on same addr", !success);
	assert_invariants (anal);
	mu_assert_eq (block->addr, 0x200, "addr after failed relocate");
	mu_assert_eq (block->size, 0x100, "size after failed relocate");
	r_anal_function_linear_size (fcn); // trigger lazy calculation of min/max cache
	assert_invariants (anal);

	// jump after the other block
	success = r_anal_block_relocate (block, 0x500, 0x333);
	mu_assert ("relocate success", success);
	assert_invariants (anal);
	mu_assert_eq (block->addr, 0x500, "addr after failed relocate");
	mu_assert_eq (block->size, 0x333, "size after failed relocate");
	r_anal_function_linear_size (fcn); // trigger lazy calculation of min/max cache
	assert_invariants (anal);

	// jump before the other block
	success = r_anal_block_relocate (block, 0x10, 0x333);
	mu_assert ("relocate success", success);
	assert_invariants (anal);
	mu_assert_eq (block->addr, 0x10, "addr after failed relocate");
	mu_assert_eq (block->size, 0x333, "size after failed relocate");

	r_unref (block);
	r_unref (second);
	assert_invariants (anal);

	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_query(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

#define N 200
#define MAXSIZE 0x300
#define SPACE 0x10000
#define SAMPLES 300

	RAnalBlock *blocks[N];
	size_t i;
	for (i = 0; i < N; i++) {
		blocks[i] = r_anal_create_block (anal, rand () % SPACE, rand () % MAXSIZE); // may return null on duplicates
		assert_invariants (anal);
	}

	// --
	// test r_anal_get_block_at()

	for (i = 0; i < N; i++) {
		if (!blocks[i]) {
			continue;
		}
		mu_assert_ptreq (r_anal_get_block_at (anal, blocks[i]->addr), blocks[i], "r_anal_get_block_at");
	}

	for (i = 0; i < SAMPLES; i++) {
		ut64 addr = rand () % SPACE;
		size_t j;

		// goal is to check cases where r_anal_get_block_at() returns null,
		// but since the addr is random, there may be a block sometimes too.
		RAnalBlock *block = NULL;
		for (j = 0; j < N; j++) {
			if (!blocks[j]) {
				continue;
			}
			if (blocks[j]->addr == addr) {
				block = blocks[j];
				break;
			}
		}

		mu_assert_ptreq (r_anal_get_block_at (anal, addr), block, "r_anal_get_block_at");
	}

	// --
	// test r_anal_get_blocks_in()

	for (i = 0; i < SAMPLES; i++) {
		ut64 addr = rand () % SPACE;
		RList *in = r_anal_get_blocks_in (anal, addr);

		RAnalBlock *block;
		RListIter *it;
		r_list_foreach (in, it, block) {
			mu_assert_eq (r_ref_count (block), 2, "block refd in returned list");
		}

		size_t linear_found = 0;
		size_t j;
		for (j = 0; j < N; j++) {
			if (!blocks[j]) {
				continue;
			}
			if (r_anal_block_contains (blocks[j], addr)) {
				linear_found++;
				mu_assert ("intersect linear found in list", r_list_contains (in, blocks[j]));
			}
		}
		mu_assert_eq ((size_t)r_list_length (in), linear_found, "r_anal_get_blocks_in count");
		r_list_free (in);
	}

	// --
	// test r_anal_get_blocks_intersect()

	for (i = 0; i < SAMPLES; i++) {
		ut64 addr = rand () % SPACE;
		ut64 size = rand() % MAXSIZE;
		RList *in = r_anal_get_blocks_intersect (anal, addr, size);

		RAnalBlock *block;
		RListIter *it;
		r_list_foreach (in, it, block) {
			mu_assert_eq (r_ref_count (block), 2, "block refd in returned list");
		}

		size_t linear_found = 0;
		size_t j;
		for (j = 0; j < N; j++) {
			RAnalBlock *block = blocks[j];
			if (!block || addr + size <= block->addr || addr >= block->addr + block->size) {
				continue;
			}
			linear_found++;
			mu_assert ("in linear found in list", r_list_contains (in, blocks[j]));
		}
		mu_assert_eq ((size_t)r_list_length (in), linear_found, "r_anal_get_blocks_intersect count");
		r_list_free (in);
	}

	for (i = 0; i < N; i++) {
		r_unref (blocks[i]);
	}

	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

static bool addr_list_contains(RList *list, ut64 addr) {
	RListIter *it;
	ut64 *item;
	r_list_foreach (list, it, item) {
		if (*item == addr) {
			return true;
		}
	}
	return false;
}

static bool addr_list_cb(ut64 addr, void *user) {
	RList *list = user;
	ut64 *item = R_NEW (ut64);
	*item = addr;
	r_list_push (list, item);
	return true;
}

static bool block_addr_list_cb(RAnalBlock *block, void *user) {
	return addr_list_cb (block->addr, user);
}

bool test_r_anal_block_recurse_missing_successors(void) {
	RAnal *anal = r_anal_new ();
	RAnalBlock *a = r_anal_create_block (anal, 0x100, 0x10);
	RAnalBlock *b = r_anal_create_block (anal, 0x110, 0x10);
	RAnalBlock *c = r_anal_create_block (anal, 0x120, 0x10);
	a->jump = 0;
	a->fail = b->addr;
	b->fail = 0xdead;
	r_anal_block_add_switch_case (b, b->addr, 1, c->addr);
	RList *visited = r_list_newf (free);
	r_anal_block_recurse_depth_first (a, block_addr_list_cb, block_addr_list_cb, visited);
	mu_assert_eq (r_list_length (visited), 6, "enter and exit all reachable blocks");
	mu_assert_eq (*(ut64 *)r_list_get_n (visited, 1), b->addr, "missing jump keeps fallthrough");
	mu_assert_eq (*(ut64 *)r_list_get_n (visited, 2), c->addr, "missing fail keeps switch cases");
	mu_assert_eq (*(ut64 *)r_list_get_n (visited, 5), a->addr, "root exits last");
	r_list_free (visited);
	r_unref (a);
	r_unref (b);
	r_unref (c);
	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_successors(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalBlock *blocks[10];
	blocks[0] = r_anal_create_block (anal, 0x10, 0x10);
	blocks[1] = r_anal_create_block (anal, 0x30, 0x10);
	blocks[2] = r_anal_create_block (anal, 0x50, 0x10);
	blocks[3] = r_anal_create_block (anal, 0x100, 0x10);
	blocks[4] = r_anal_create_block (anal, 0x110, 0x10);
	blocks[5] = r_anal_create_block (anal, 0x120, 0x10);
	blocks[6] = r_anal_create_block (anal, 0x130, 0x10);
	blocks[7] = r_anal_create_block (anal, 0x140, 0x10);
	blocks[8] = r_anal_create_block (anal, 0xa0, 0x10);
	blocks[9] = r_anal_create_block (anal, 0xc0, 0x10);
	assert_invariants (anal);

	blocks[0]->jump = 0x30;
	blocks[0]->fail = 0x50;
	blocks[1]->jump = 0x10;
	blocks[1]->fail = 0x50;
	blocks[2]->jump = 0x10;

	RAnalSwitchOp *sop = r_anal_switch_op_new (0x55, 0x13, 0x15, 0x42);
	mu_assert_eq (sop->addr, 0x55, "addr");
	mu_assert_eq (sop->min_val, 0x13, "addr");
	mu_assert_eq (sop->max_val, 0x15, "addr");
	mu_assert_eq (sop->def_val, 0x42, "addr");
	r_anal_switch_op_add_case (sop, 0x55, 1, 0x100);
	r_anal_switch_op_add_case (sop, 0x55, 2, 0x110);
	r_anal_switch_op_add_case (sop, 0x55, 3, 0x120);
	r_anal_switch_op_add_case (sop, 0x55, 4, 0x130);
	r_anal_switch_op_add_case (sop, 0x55, 5, 0x140);
	blocks[2]->switch_op = sop;

	RList *result = r_list_newf (free);
	r_anal_block_successor_addrs_foreach (blocks[0], addr_list_cb, result);
	mu_assert_eq (r_list_length (result), 2, "jump/fail successors count");
	mu_assert ("jmp successor", addr_list_contains (result, 0x30));
	mu_assert ("fail successor", addr_list_contains (result, 0x50));
	r_list_purge (result);

	r_anal_block_successor_addrs_foreach (blocks[2], addr_list_cb, result);
	mu_assert_eq (r_list_length (result), 7, "switch successors count");
	mu_assert ("jmp successor", addr_list_contains (result, 0x10));
	mu_assert ("case successor", addr_list_contains (result, 0x100));
	mu_assert ("case successor", addr_list_contains (result, 0x110));
	mu_assert ("case successor", addr_list_contains (result, 0x120));
	mu_assert ("case successor", addr_list_contains (result, 0x130));
	mu_assert ("case successor", addr_list_contains (result, 0x140));
	mu_assert ("default successor", addr_list_contains (result, 0x42));
	r_list_purge (result);

	// a default already reachable as the jump or a case is not repeated, and 0
	// is the unset value both r_anal_switch_op_new call sites leave behind
	sop->def_val = 0x120;
	r_anal_block_successor_addrs_foreach (blocks[2], addr_list_cb, result);
	mu_assert_eq (r_list_length (result), 6, "default duplicating a case");
	r_list_purge (result);
	sop->def_val = 0x10;
	r_anal_block_successor_addrs_foreach (blocks[2], addr_list_cb, result);
	mu_assert_eq (r_list_length (result), 6, "default duplicating the jump");
	r_list_purge (result);
	sop->def_val = 0;
	r_anal_block_successor_addrs_foreach (blocks[2], addr_list_cb, result);
	mu_assert_eq (r_list_length (result), 6, "unset default is no successor");
	sop->def_val = 0x42;
	r_list_free (result);

	result = r_anal_block_recurse_list (blocks[0]);
	RAnalBlock *block;
	RListIter *it;
	r_list_foreach (result, it, block) {
		mu_assert_eq (r_ref_count (block), 2, "block refd in returned list");
	}

	mu_assert_eq (r_list_length (result), 8, "recursive successors count");
	mu_assert ("recursive successor", r_list_contains (result, blocks[0]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[1]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[2]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[3]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[4]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[5]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[6]));
	mu_assert ("recursive successor", r_list_contains (result, blocks[7]));

	r_list_free (result);

	size_t i;
	for (i = 0; i < sizeof (blocks) / sizeof (RAnalBlock *); i++) {
		r_unref (blocks[i]);
	}

	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_switch_spec_limits_ncases(void) {
	RAnalSwitchSpec spec;
	r_anal_switch_spec_legacy (&spec, 0x10, 0x20, 4, R_ANAL_SWITCH_MAXCASES + 1, 0);
	mu_assert_eq (spec.ncases, R_ANAL_SWITCH_MAXCASES, "legacy switch ncases limit");
	mu_end;
}

bool test_r_anal_block_automerge(void) {
	size_t i;
	for (i = 0; i < SAMPLES; i++) {
		RAnal *anal = r_anal_new ();
		assert_invariants (anal);

		RAnalBlock *a = r_anal_create_block (anal, 0x100, 0x10);

		RAnalBlock *b = r_anal_create_block (anal, 0x110, 0x10);
		a->jump = b->addr;

		RAnalBlock *c = r_anal_create_block (anal, 0x120, 0x10);
		b->jump = c->addr;
		c->fail = b->addr;

		RAnalBlock *d = r_anal_create_block (anal, 0x130, 0x10);
		c->jump = d->addr;

		RAnalBlock *e = r_anal_create_block (anal, 0x140, 0x10);
		d->jump = e->addr;

		RAnalBlock *f = r_anal_create_block (anal, 0x150, 0x10);
		e->jump = f->addr;

		RAnalFunction *fa = r_anal_create_function (anal, "fcn", 0x100, R_ANAL_FCN_TYPE_FCN, NULL);
		r_anal_function_add_block (fa, a);
		r_anal_function_add_block (fa, c);
		r_anal_function_add_block (fa, d);
		r_anal_function_add_block (fa, e);
		r_anal_function_add_block (fa, f);

		RAnalFunction *fb = r_anal_create_function (anal, "fcn2", 0x110, R_ANAL_FCN_TYPE_FCN, NULL);
		r_anal_function_add_block (fb, b);
		r_anal_function_add_block (fb, c);
		r_anal_function_add_block (fb, d);
		r_anal_function_add_block (fb, e);
		r_anal_function_add_block (fb, f);

		RList *all_blocks = r_list_new ();
		r_list_push (all_blocks, a);
		r_list_push (all_blocks, b);
		r_list_push (all_blocks, c);
		r_list_push (all_blocks, d);
		r_list_push (all_blocks, e);
		r_list_push (all_blocks, f);

		// Randomize the order in which we give the automerge the block.
		// The outcome should always be the same but it can have some delicate implications on the algorithm inside.
		RList *shuffled_blocks = r_list_newf ((RListFree)block_unref);
		while (!r_list_empty (all_blocks)) {
			int n = rand () % r_list_length (all_blocks);
			r_list_push (shuffled_blocks, r_list_get_n (all_blocks, n));
			r_list_del_n (all_blocks, n);
		}
		r_list_free (all_blocks);

		r_anal_block_automerge (shuffled_blocks);
		assert_invariants (anal);
		//mu_assert_eq (r_list_length (shuffled_blocks), 4, "length after automerge");
		mu_assert ("remaining blocks a", r_list_contains (shuffled_blocks, a));
		mu_assert ("remaining blocks b", r_list_contains (shuffled_blocks, b));
		mu_assert ("remaining blocks c", r_list_contains (shuffled_blocks, c));
		mu_assert ("remaining blocks d", r_list_contains (shuffled_blocks, d));
		mu_assert_eq (blocks_count (anal), r_list_length (shuffled_blocks), "blocks in anal count");
		RListIter *it;
		RAnalBlock *block;
		r_list_foreach (shuffled_blocks, it, block) {
			mu_assert_ptreq (r_anal_get_block_at (anal, block->addr), block, "remaining blocks in anal");
		}
		r_list_free (shuffled_blocks);

		assert_invariants (anal);
		assert_leaks (anal);
		r_anal_free (anal);
	}
	mu_end;
}

bool test_r_anal_block_chop_noreturn(void) {
	RAnal *anal = r_anal_new ();
	assert_invariants (anal);

	RAnalBlock *a = r_anal_create_block (anal, 0x100, 0x10);
	RAnalBlock *b = r_anal_create_block (anal, 0x110, 0x10);
	RAnalBlock *c = r_anal_create_block (anal, 0x120, 0x10);
	a->jump = c->addr;
	b->jump = c->addr;

	RAnalFunction *fa = r_anal_create_function (anal, "fcn", 0x100, R_ANAL_FCN_TYPE_FCN, NULL);
	r_anal_function_add_block (fa, a);
	r_anal_function_add_block (fa, b);
	r_anal_function_add_block (fa, c);

	RAnalFunction *fb = r_anal_create_function (anal, "fcn2", 0x130, R_ANAL_FCN_TYPE_FCN, NULL);
	fb->is_noreturn = true;

	r_anal_block_chop_noreturn (b, 0x111);

	assert_invariants (anal);
	r_anal_free (anal);

	mu_end;
}

bool test_r_anal_block_chop_noreturn_shared_roots(void) {
	RAnal *anal = r_anal_new ();
	RAnalFunction *with_pads = r_anal_create_function (anal, "with_pads", 0x100, R_ANAL_FCN_TYPE_FCN, NULL);
	RAnalFunction *without_pads = r_anal_create_function (anal, "without_pads", 0x700, R_ANAL_FCN_TYPE_FCN, NULL);
	RAnalBlock *entry = r_anal_create_block (anal, 0x100, 0x10);
	RAnalBlock *chopped = r_anal_create_block (anal, 0x200, 0x10);
	RAnalBlock *branch = r_anal_create_block (anal, 0x300, 0x10);
	RAnalBlock *join = r_anal_create_block (anal, 0x350, 0x10);
	RAnalBlock *tail = r_anal_create_block (anal, 0x380, 0x10);
	RAnalBlock *dead = r_anal_create_block (anal, 0x400, 0x10);
	RAnalBlock *pad = r_anal_create_block (anal, 0x500, 0x10);
	RAnalBlock *other_pad = r_anal_create_block (anal, 0x600, 0x10);
	RAnalBlock *other_entry = r_anal_create_block (anal, 0x700, 0x10);
	entry->jump = chopped->addr;
	other_entry->jump = chopped->addr;
	chopped->jump = branch->addr;
	chopped->fail = dead->addr;
	branch->jump = join->addr;
	branch->fail = tail->addr;
	join->jump = branch->addr;
	pad->jump = branch->addr;
	other_pad->jump = join->addr;

	r_anal_function_add_block (with_pads, entry);
	r_anal_function_add_block (with_pads, pad);
	r_anal_function_add_block (with_pads, other_pad);
	r_anal_function_add_block (without_pads, other_entry);
	RAnalBlock *shared[] = { chopped, branch, join, tail, dead };
	size_t i;
	for (i = 0; i < R_ARRAY_SIZE (shared); i++) {
		r_anal_function_add_block (with_pads, shared[i]);
		r_anal_function_add_block (without_pads, shared[i]);
	}

	mu_assert_ptreq (r_anal_block_chop_noreturn (chopped, 0x201), chopped, "chopped block survives");
	mu_assert_eq (r_list_length (with_pads->bbs), 7, "landing pads keep their shared suffix");
	mu_assert ("cycle remains reachable", r_list_contains (with_pads->bbs, join));
	mu_assert ("other branch remains reachable", r_list_contains (with_pads->bbs, tail));
	mu_assert ("unreachable successor removed", !r_list_contains (with_pads->bbs, dead));
	mu_assert_eq (r_list_length (without_pads->bbs), 2, "reachability resets for each function");
	mu_assert ("second function keeps chopped block", r_list_contains (without_pads->bbs, chopped));
	mu_assert_ptreq (r_anal_block_chop_noreturn (chopped, 0x201), chopped, "repeated chop survives");
	mu_assert_eq (r_list_length (with_pads->bbs), 7, "repeated chop keeps landing pads");
	mu_assert_eq (r_list_length (without_pads->bbs), 2, "repeated chop keeps second function");

	r_unref (entry);
	r_unref (pad);
	r_unref (other_pad);
	r_unref (other_entry);
	for (i = 0; i < R_ARRAY_SIZE (shared); i++) {
		r_unref (shared[i]);
	}
	assert_invariants (anal);
	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

bool test_r_anal_block_chop_noreturn_contiguous(void) {
	RAnal *anal = r_anal_new ();
	RAnalFunction *fcn = r_anal_create_function (anal, "contiguous", 0x100, R_ANAL_FCN_TYPE_FCN, NULL);
	RAnalBlock *entry = r_anal_create_block (anal, 0x100, 0x10);
	RAnalBlock *chopped = r_anal_create_block (anal, 0x110, 0x10);
	entry->jump = chopped->addr;
	r_anal_function_add_block (fcn, entry);
	r_anal_function_add_block (fcn, chopped);

	mu_assert_null (r_anal_block_chop_noreturn (chopped, 0x111), "chopped block merged into entry");
	mu_assert_eq (entry->size, 0x11, "entry includes shortened block");
	mu_assert_eq (entry->jump, UT64_MAX, "merged block has no successor");
	mu_assert_eq (r_list_length (fcn->bbs), 1, "contiguous predecessor still merges");
	r_unref (entry);
	assert_invariants (anal);
	assert_leaks (anal);
	r_anal_free (anal);
	mu_end;
}

int all_tests(void) {
	mu_run_test (test_r_anal_block_chop_noreturn);
	mu_run_test (test_r_anal_block_chop_noreturn_shared_roots);
	mu_run_test (test_r_anal_block_chop_noreturn_contiguous);
	mu_run_test (test_r_anal_block_create);
	mu_run_test (test_r_anal_block_contains);
	mu_run_test (test_r_anal_block_split);
	mu_run_test (test_r_anal_block_split_in_function);
	mu_run_test (test_r_anal_block_merge);
	mu_run_test (test_r_anal_block_merge_in_function);
	mu_run_test (test_r_anal_block_delete);
	mu_run_test (test_r_anal_block_reset);
	mu_run_test (test_r_anal_block_reset_refs);
	mu_run_test (test_r_anal_block_set_size);
	mu_run_test (test_r_anal_block_relocate);
	mu_run_test (test_r_anal_block_query);
	mu_run_test (test_r_anal_block_successors);
	mu_run_test (test_r_anal_block_recurse_missing_successors);
	mu_run_test (test_r_anal_switch_spec_limits_ncases);
	mu_run_test (test_r_anal_block_automerge);
	return tests_passed != tests_run;
}

int main(int argc, char **argv) {
	struct timeval tv;
	gettimeofday (&tv, NULL);
	unsigned int seed = argc > 1 ? strtoul (argv[1], NULL, 0) : tv.tv_sec + tv.tv_usec;
	printf("seed for test_anal_block: %u\n", seed);
	return all_tests();
}
