* Refactor del_edges to use RzGraphStatus. * Refactor del_edge() to use RzGraphStatus. * Refactor update_edge() to use RzGraphStatus. * Refactor has_edge() to use RzGraphStatus. * Refactor add_edge() to use RzGraphStatus. * Fix leak of b * Fix leak of xref list * Fix leaks of analysis ops * Address review comments * Inlcude clean up * Fix invalid free * Add tests with node and edge data. * Extend tests
408 lines
14 KiB
C
408 lines
14 KiB
C
// SPDX-FileCopyrightText: 2021 ret2libc <sirmy15@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_util.h>
|
|
#include "minunit.h"
|
|
#include "rz_util/rz_graph.h"
|
|
|
|
static ut64 ptr_hash(const void *data) {
|
|
return (ut64)(uintptr_t)data;
|
|
}
|
|
|
|
static void topo_sorting(RzGraphNode *n, RzGraphVisitor *vis) {
|
|
RzList *order = (RzList *)vis->visitor_data;
|
|
rz_list_prepend(order, n);
|
|
}
|
|
|
|
static bool test_legacy_graph(void) {
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
|
|
mu_assert_eq(rz_graph_count_nodes(g), 0, "n_nodes.start");
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, NULL), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
mu_assert_eq(rz_graph_count_nodes(g), 1, "n_nodes.insert");
|
|
rz_graph_reset(g);
|
|
mu_assert_eq(rz_graph_count_nodes(g), 0, "n_nodes.reset");
|
|
|
|
RzGraphNode *gn = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &gn), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
mu_assert_ptreq(rz_graph_find_node(g, rz_graph_node_get_id(gn)), gn, "get_node.1");
|
|
RzGraphNode *gn2 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &gn2), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
mu_assert_ptreq(rz_graph_find_node(g, rz_graph_node_get_id(gn2)), gn2, "get_node.2");
|
|
rz_graph_add_edge(g, gn, gn2, NULL);
|
|
mu_assert_eq(rz_graph_has_edge(g, gn, gn2), RZ_GRAPH_STATUS_OK, "is_adjacent.1");
|
|
|
|
// Check out-neighbors of gn: should contain gn2
|
|
{
|
|
RzIterator *it = rz_graph_out_neighbors(g, gn);
|
|
mu_assert_notnull(it, "get_neighbours.1.iter");
|
|
int count = 0;
|
|
RzGraphNode *nb;
|
|
rz_iterator_foreach(it, nb) {
|
|
mu_assert_ptreq(nb, gn2, "get_neighbours.1");
|
|
count++;
|
|
}
|
|
mu_assert_eq(count, 1, "get_neighbours.1.count");
|
|
rz_iterator_free(it);
|
|
}
|
|
|
|
RzGraphNode *gn3 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &gn3), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, gn, gn3, NULL);
|
|
|
|
// Check out-neighbors of gn: gn2 and gn3
|
|
{
|
|
RzIterator *it = rz_graph_out_neighbors(g, gn);
|
|
mu_assert_notnull(it, "get_neighbours.2.iter");
|
|
int count = 0;
|
|
RzGraphNode *nb;
|
|
rz_iterator_foreach(it, nb) {
|
|
mu_assert_true(nb == gn2 || nb == gn3, "get_neighbours.2");
|
|
count++;
|
|
}
|
|
mu_assert_eq(count, 2, "get_neighbours.2.count");
|
|
rz_iterator_free(it);
|
|
}
|
|
|
|
RzGraphNode *gn4 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)4, &gn4), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn5 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)5, &gn5), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn6 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)6, &gn6), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn7 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)7, &gn7), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn8 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)8, &gn8), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn9 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)9, &gn9), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *gn10 = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)10, &gn10), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
mu_assert_eq(rz_graph_count_nodes(g), 10, "n_nodes.again");
|
|
|
|
// Check all nodes are present
|
|
{
|
|
RzIterator *it = rz_graph_get_nodes(g);
|
|
mu_assert_notnull(it, "get_all_nodes.iter");
|
|
int count = 0;
|
|
RzGraphNode *nd;
|
|
rz_iterator_foreach(it, nd) {
|
|
count++;
|
|
}
|
|
mu_assert_eq(count, 10, "get_all_nodes.count");
|
|
rz_iterator_free(it);
|
|
}
|
|
|
|
rz_graph_add_edge(g, gn2, gn3, NULL);
|
|
rz_graph_add_edge(g, gn2, gn4, NULL);
|
|
rz_graph_add_edge(g, gn2, gn5, NULL);
|
|
rz_graph_add_edge(g, gn3, gn5, NULL);
|
|
rz_graph_add_edge(g, gn5, gn7, NULL);
|
|
rz_graph_add_edge(g, gn7, gn9, NULL);
|
|
rz_graph_add_edge(g, gn9, gn10, NULL);
|
|
rz_graph_add_edge(g, gn4, gn6, NULL);
|
|
rz_graph_add_edge(g, gn6, gn8, NULL);
|
|
rz_graph_add_edge(g, gn6, gn9, NULL);
|
|
rz_graph_add_edge(g, gn8, gn10, NULL);
|
|
|
|
rz_graph_add_edge(g, gn5, gn4, NULL);
|
|
rz_graph_add_edge(g, gn6, gn7, NULL);
|
|
rz_graph_add_edge(g, gn7, gn8, NULL);
|
|
rz_graph_add_edge(g, gn8, gn9, NULL);
|
|
mu_assert_eq(rz_graph_count_edges(g), 17, "n_edges");
|
|
mu_assert_eq(rz_graph_del_edge(g, gn8, gn9), RZ_GRAPH_STATUS_EXISTED, "del edge failed");
|
|
mu_assert_eq(rz_graph_has_edge(g, gn8, gn9), RZ_GRAPH_STATUS_MISSING_EDGE, "is_adjacent.0");
|
|
mu_assert_eq(rz_graph_count_edges(g), 16, "n_edges.1");
|
|
rz_graph_add_edge(g, gn9, gn8, NULL);
|
|
mu_assert_eq(rz_graph_count_edges(g), 17, "n_edges.2");
|
|
mu_assert_eq(rz_graph_has_edge(g, gn9, gn8), RZ_GRAPH_STATUS_OK, "is_adjacent");
|
|
mu_assert_eq(rz_graph_del_edge(g, gn9, gn8), RZ_GRAPH_STATUS_EXISTED, "del edge failed");
|
|
rz_graph_add_edge(g, gn8, gn9, NULL);
|
|
mu_assert_eq(rz_graph_has_edge(g, gn9, gn8), RZ_GRAPH_STATUS_MISSING_EDGE, "is_adjacent.1");
|
|
mu_assert_eq(rz_graph_has_edge(g, gn8, gn9), RZ_GRAPH_STATUS_OK, "is_adjacent.2");
|
|
|
|
RzGraphVisitor vis = { 0 };
|
|
vis.visitor_data = rz_list_new();
|
|
vis.finish_node = topo_sorting;
|
|
rz_graph_dfs_from_node(g, gn, &vis);
|
|
mu_assert_eq(rz_list_length((RzList *)vis.visitor_data), 10, "topo_order.count");
|
|
rz_list_free((RzList *)vis.visitor_data);
|
|
|
|
// Check in-neighbors of gn3: gn and gn2
|
|
{
|
|
RzIterator *it = rz_graph_in_neighbors(g, gn3);
|
|
mu_assert_notnull(it, "in_nodes.iter");
|
|
int count = 0;
|
|
RzGraphNode *nb;
|
|
rz_iterator_foreach(it, nb) {
|
|
mu_assert_true(nb == gn || nb == gn2, "in_nodes");
|
|
count++;
|
|
}
|
|
mu_assert_eq(count, 2, "in_nodes.count");
|
|
rz_iterator_free(it);
|
|
}
|
|
|
|
// All neighbors of gn3: in={gn, gn2} + out={gn5}
|
|
{
|
|
int count = 0;
|
|
RzGraphNode *nb;
|
|
RzIterator *it = rz_graph_in_neighbors(g, gn3);
|
|
rz_iterator_foreach(it, nb) {
|
|
mu_assert_true(nb == gn || nb == gn2, "all_neighbours.in");
|
|
count++;
|
|
}
|
|
rz_iterator_free(it);
|
|
it = rz_graph_out_neighbors(g, gn3);
|
|
rz_iterator_foreach(it, nb) {
|
|
mu_assert_ptreq(nb, gn5, "all_neighbours.out");
|
|
count++;
|
|
}
|
|
rz_iterator_free(it);
|
|
mu_assert_eq(count, 3, "all_neighbours.count");
|
|
}
|
|
|
|
mu_assert_eq(rz_graph_del_node(g, gn), RZ_GRAPH_STATUS_EXISTED, "Wrong return value");
|
|
mu_assert_eq(rz_graph_del_node(g, gn2), RZ_GRAPH_STATUS_EXISTED, "Wrong return value");
|
|
mu_assert_eq(rz_graph_count_nodes(g), 8, "n_nodes.del_node");
|
|
mu_assert_eq(rz_graph_count_edges(g), 12, "n_edges.del_node");
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
/* =========================================================================
|
|
* Tests for rz_graph_find_back_edges
|
|
* ========================================================================= */
|
|
|
|
static bool test_find_back_edges_simple(void) {
|
|
/* A -> B -> C -> A (single cycle, back edge = C->A) */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, b, c, NULL);
|
|
rz_graph_add_edge(g, c, a, NULL);
|
|
|
|
RzList *back = rz_graph_find_back_edges(g, NULL, NULL);
|
|
mu_assert_notnull(back, "back_edges not null");
|
|
mu_assert_eq(rz_list_length(back), 1, "one back edge");
|
|
RzGraphEdge *e = rz_list_get_n(back, 0);
|
|
mu_assert_ptreq(rz_graph_edge_get_from(e), c, "back edge from C");
|
|
mu_assert_ptreq(rz_graph_edge_get_to(e), a, "back edge to A");
|
|
rz_list_free(back);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_find_back_edges_dag(void) {
|
|
/* DAG: A->B, A->C, B->D, C->D — no back edges */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *d = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)4, &d), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, a, c, NULL);
|
|
rz_graph_add_edge(g, b, d, NULL);
|
|
rz_graph_add_edge(g, c, d, NULL);
|
|
|
|
RzList *back = rz_graph_find_back_edges(g, NULL, NULL);
|
|
mu_assert_notnull(back, "back_edges not null");
|
|
mu_assert_eq(rz_list_length(back), 0, "no back edges in DAG");
|
|
rz_list_free(back);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_find_back_edges_self_loop(void) {
|
|
/* A -> A (self loop) */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, a, NULL);
|
|
|
|
RzList *back = rz_graph_find_back_edges(g, NULL, NULL);
|
|
mu_assert_notnull(back, "back_edges not null");
|
|
mu_assert_eq(rz_list_length(back), 1, "self-loop is a back edge");
|
|
rz_list_free(back);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_find_back_edges_multiple_cycles(void) {
|
|
/* Two independent cycles: A->B->A and C->D->C */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *d = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)4, &d), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, b, a, NULL);
|
|
rz_graph_add_edge(g, c, d, NULL);
|
|
rz_graph_add_edge(g, d, c, NULL);
|
|
|
|
RzList *back = rz_graph_find_back_edges(g, NULL, NULL);
|
|
mu_assert_notnull(back, "back_edges not null");
|
|
mu_assert_eq(rz_list_length(back), 2, "two back edges for two cycles");
|
|
rz_list_free(back);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
/* =========================================================================
|
|
* Tests for rz_graph_find_sccs
|
|
* ========================================================================= */
|
|
|
|
static bool test_find_sccs_dag(void) {
|
|
/* DAG: A->B->D, A->C->D — each node is its own SCC */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *d = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)4, &d), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, a, c, NULL);
|
|
rz_graph_add_edge(g, b, d, NULL);
|
|
rz_graph_add_edge(g, c, d, NULL);
|
|
|
|
RzPVector *sccs = rz_graph_find_sccs(g);
|
|
mu_assert_notnull(sccs, "sccs not null");
|
|
mu_assert_eq(rz_pvector_len(sccs), 4, "four trivial SCCs in DAG");
|
|
/* Each SCC must contain exactly one node */
|
|
void **it;
|
|
rz_pvector_foreach (sccs, it) {
|
|
RzPVector *scc = (RzPVector *)*it;
|
|
mu_assert_eq(rz_pvector_len(scc), 1, "each SCC size is 1");
|
|
}
|
|
rz_pvector_free(sccs);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_find_sccs_single_cycle(void) {
|
|
/* A->B->C->A forms one SCC of size 3 */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, b, c, NULL);
|
|
rz_graph_add_edge(g, c, a, NULL);
|
|
|
|
RzPVector *sccs = rz_graph_find_sccs(g);
|
|
mu_assert_notnull(sccs, "sccs not null");
|
|
mu_assert_eq(rz_pvector_len(sccs), 1, "one SCC");
|
|
RzPVector *scc = rz_pvector_at(sccs, 0);
|
|
mu_assert_eq(rz_pvector_len(scc), 3, "SCC contains all 3 nodes");
|
|
/* Verify all nodes are present */
|
|
bool has_a = false, has_b = false, has_c = false;
|
|
void **it;
|
|
rz_pvector_foreach (scc, it) {
|
|
RzGraphNode *n = (RzGraphNode *)*it;
|
|
if (n == a)
|
|
has_a = true;
|
|
if (n == b)
|
|
has_b = true;
|
|
if (n == c)
|
|
has_c = true;
|
|
}
|
|
mu_assert_true(has_a && has_b && has_c, "all cycle nodes in SCC");
|
|
rz_pvector_free(sccs);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_find_sccs_mixed(void) {
|
|
/* Two cycles connected by a DAG edge:
|
|
* A->B->A (SCC1={A,B})
|
|
* A->C->D->C (SCC2={C,D}), C is also reachable from A
|
|
* Expected: SCC {A,B}, SCC {C,D} */
|
|
RzGraph *g = rz_graph_new(RZ_GRAPH_IMPL_LIST, ptr_hash, NULL, NULL);
|
|
RzGraphNode *a = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)1, &a), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *b = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)2, &b), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *c = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)3, &c), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
RzGraphNode *d = NULL;
|
|
mu_assert_eq(rz_graph_add_node(g, (void *)4, &d), RZ_GRAPH_STATUS_OK, "Failed add");
|
|
rz_graph_add_edge(g, a, b, NULL);
|
|
rz_graph_add_edge(g, b, a, NULL);
|
|
rz_graph_add_edge(g, a, c, NULL);
|
|
rz_graph_add_edge(g, c, d, NULL);
|
|
rz_graph_add_edge(g, d, c, NULL);
|
|
|
|
RzPVector *sccs = rz_graph_find_sccs(g);
|
|
mu_assert_notnull(sccs, "sccs not null");
|
|
mu_assert_eq(rz_pvector_len(sccs), 2, "two SCCs");
|
|
|
|
bool found_ab = false, found_cd = false;
|
|
void **it;
|
|
rz_pvector_foreach (sccs, it) {
|
|
RzPVector *scc = (RzPVector *)*it;
|
|
if (rz_pvector_len(scc) == 2) {
|
|
bool ha = false, hb = false, hc = false, hd = false;
|
|
void **jt;
|
|
rz_pvector_foreach (scc, jt) {
|
|
RzGraphNode *n = (RzGraphNode *)*jt;
|
|
if (n == a)
|
|
ha = true;
|
|
if (n == b)
|
|
hb = true;
|
|
if (n == c)
|
|
hc = true;
|
|
if (n == d)
|
|
hd = true;
|
|
}
|
|
if (ha && hb)
|
|
found_ab = true;
|
|
if (hc && hd)
|
|
found_cd = true;
|
|
}
|
|
}
|
|
mu_assert_true(found_ab, "SCC {A,B} found");
|
|
mu_assert_true(found_cd, "SCC {C,D} found");
|
|
rz_pvector_free(sccs);
|
|
|
|
rz_graph_free(g);
|
|
mu_end;
|
|
}
|
|
|
|
static int all_tests() {
|
|
mu_run_test(test_legacy_graph);
|
|
mu_run_test(test_find_back_edges_simple);
|
|
mu_run_test(test_find_back_edges_dag);
|
|
mu_run_test(test_find_back_edges_self_loop);
|
|
mu_run_test(test_find_back_edges_multiple_cycles);
|
|
mu_run_test(test_find_sccs_dag);
|
|
mu_run_test(test_find_sccs_single_cycle);
|
|
mu_run_test(test_find_sccs_mixed);
|
|
return tests_passed != tests_run;
|
|
}
|
|
|
|
mu_main(all_tests)
|