* Fix revert if adding a node failed. * Reduce allocations by keeping only a single RzGraphEdge object per edge around. * Add benchmark for graph deletion and addition of nodes/edges * Use rz_pvector_remove_at_unsorted to save some runtime. * Use realloc and memmove for matrix graphs on capacity increase. * Add helper to determine memory usage. * Add benchmark * Revert matrix capacity extension to simple and jsut as fast loop. * Missing type annotations
2200 lines
65 KiB
C
2200 lines
65 KiB
C
// SPDX-FileCopyrightText: 2025-2026 heersin <teablearcher@gmail.com>
|
|
// SPDX-FileCopyrightText: 2007-2020 pancake <pancake@nopcode.org>
|
|
// SPDX-FileCopyrightText: 2007-2020 ret2libc <sirmy15@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_util/rz_graph.h>
|
|
#include <rz_types.h>
|
|
#include <rz_util/rz_str.h>
|
|
#include <rz_util/rz_strbuf.h>
|
|
#include <rz_vector.h>
|
|
|
|
#include "graph_priv.h"
|
|
|
|
/**
|
|
* \brief Default size of the edge vector in a list based graph implementation.
|
|
*/
|
|
#define LIST_IMPL_DEFAULT_EDGE_VEC_SIZE 4
|
|
/**
|
|
* \brief Default size of the nodes' edge vectors in a list based graph implementation.
|
|
*/
|
|
#define LIST_IMPL_DEFAULT_NODE_VEC_SIZE 16
|
|
|
|
typedef struct rz_graph_list_edge_impl_t {
|
|
RzPVector /*<RzPVector<RZ_OWN RzGraphEdge *>*/ *in_edges; ///< maps node hash_id to its incoming edge vector
|
|
RzPVector /*<RzPVector<RZ_BORROW RzGraphEdge *>*/ *out_edges; ///< maps node hash_id to its outgoing edge vector
|
|
} RzGraphListImpl;
|
|
|
|
typedef struct rz_graph_matrix_edge_impl_t {
|
|
RzGraphEdge **matrix; // index by matrix[from_vec_id][to_vec_id]
|
|
ut64 capacity;
|
|
} RzGraphMatrixImpl;
|
|
|
|
/* Edge Extract and Builds */
|
|
|
|
/**
|
|
* \brief helper func,
|
|
* create a new edge from \p from to \p to with user data.
|
|
*
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \param data user data attached to edge
|
|
* \return A new RzGraphEdgeNew or NULL on failure
|
|
*/
|
|
static RzGraphEdge *edge_new(RzGraphNode *from, RzGraphNode *to, void *data) {
|
|
RzGraphEdge *e = RZ_NEW0(RzGraphEdge);
|
|
if (!e) {
|
|
return NULL;
|
|
}
|
|
e->from = from;
|
|
e->to = to;
|
|
e->data = data;
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* \brief helper func,
|
|
* Free an edge struct only, user data is freed by graph.
|
|
* \param e edge to free
|
|
*/
|
|
// user data free by graph not edge_free
|
|
static inline void edge_free(RzGraphEdge *e) {
|
|
free(e);
|
|
}
|
|
|
|
/**
|
|
* \brief helper func,
|
|
* Create a new RzPVector to hold edges.
|
|
* \param edge_data_free optional free callback for edge user data
|
|
* \return A new RzPVector or NULL on failure
|
|
*/
|
|
static inline RZ_OWN RzPVector /*<RzGraphEdge *>*/ *edge_vec_new(RzGraphEdgeDataFree edge_data_free) {
|
|
RzPVector *edge_vec = rz_pvector_new(edge_data_free);
|
|
if (!edge_vec) {
|
|
return NULL;
|
|
}
|
|
rz_pvector_reserve(edge_vec, LIST_IMPL_DEFAULT_EDGE_VEC_SIZE);
|
|
return edge_vec;
|
|
}
|
|
|
|
/**
|
|
* \brief helper func,
|
|
* Find the index of an edge (from -> to) in graph edge set
|
|
*
|
|
* Linear scan through \p vec to locate the edge matching \p from and \p to.
|
|
*
|
|
* \param vec the edge vector to search
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return index of the edge if found, or (ut64)-1 if not found
|
|
*/
|
|
static ut64 edge_vec_find_eid(RzPVector /*<RzGraphEdge *>*/ *vec, RzGraphNode *from, RzGraphNode *to) {
|
|
void **it;
|
|
ut64 i = 0;
|
|
rz_pvector_foreach (vec, it) {
|
|
RzGraphEdge *e = (RzGraphEdge *)(*it);
|
|
if (e->from == from && e->to == to) {
|
|
return i;
|
|
}
|
|
++i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Double direction Adjacency List Impl */
|
|
|
|
/**
|
|
* \brief Add a directed edge (from -> to) in the adjacency list implementation.
|
|
*
|
|
* Inserts the edge into both the out_edges table of \p from and
|
|
* the in_edges table of \p to. Skips if the edge already exists.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \param edge_data The data attached to the edge.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge was added.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If edge existed.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
static RzGraphStatus rz_graph_list_impl_add_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to, RZ_OWN void *edge_data) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
|
|
// check output edge of from exist
|
|
RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, from->_vec_id);
|
|
|
|
// no output, cold boot to build output
|
|
if (!out_vec) {
|
|
// The out-vector just borrows the RzGraphEdge object
|
|
// from the in-vector.
|
|
out_vec = edge_vec_new(NULL);
|
|
if (!out_vec) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(edge_data);
|
|
}
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
rz_pvector_assign_at(impl->out_edges, from->_vec_id, out_vec);
|
|
}
|
|
|
|
// search edge in graph, skip if already exist
|
|
if (edge_vec_find_eid(out_vec, from, to) != -1) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(edge_data);
|
|
}
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
// check input
|
|
RzPVector /*<RzGraphEdge *>*/ *in_vec = rz_pvector_at(impl->in_edges, to->_vec_id);
|
|
if (!in_vec) {
|
|
// in-vector owns the RzGraphEdge object.
|
|
in_vec = edge_vec_new((RzGraphEdgeDataFree)edge_free);
|
|
if (!in_vec) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(edge_data);
|
|
}
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
rz_pvector_assign_at(impl->in_edges, to->_vec_id, in_vec);
|
|
}
|
|
|
|
// build out edge and in edge, and maintain the edge table
|
|
// our view: oe to carry user data, ie carry a ref copy only
|
|
RzGraphEdge *ie = edge_new(from, to, edge_data);
|
|
|
|
rz_pvector_push(out_vec, ie);
|
|
rz_pvector_push(in_vec, ie);
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
static void remove_free_edge_list(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzPVector /*<RzGraphEdge *>*/ *edges, size_t index, bool free_data) {
|
|
RzGraphEdge *e = rz_pvector_at(edges, index);
|
|
// free user data
|
|
if (free_data && g->edge_data_free && e->data) {
|
|
g->edge_data_free(e->data);
|
|
}
|
|
e->data = NULL;
|
|
edge_free(e);
|
|
rz_pvector_remove_at_unsorted(edges, index);
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a directed edge (from -> to) in the adjacency list implementation.
|
|
*
|
|
* Removes the edge from both the out_edges table of \p from and
|
|
* the in_edges table of \p to. Frees edge user data via graph callback.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return true on success, false if edge not found
|
|
*/
|
|
static RzGraphStatus rz_graph_list_impl_del_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphListImpl *impl = g->impl;
|
|
|
|
// remove from out edges
|
|
RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, from->_vec_id);
|
|
if (!out_vec || rz_pvector_empty(out_vec)) {
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
ut64 eid = edge_vec_find_eid(out_vec, from, to);
|
|
if (eid == -1) {
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
// Remove without free
|
|
rz_pvector_remove_at_unsorted(out_vec, eid);
|
|
|
|
// remove in edge
|
|
RzPVector /*<RzGraphEdge *>*/ *in_vec = rz_pvector_at(impl->in_edges, to->_vec_id);
|
|
if (in_vec) {
|
|
eid = edge_vec_find_eid(in_vec, from, to);
|
|
if (eid != -1) {
|
|
remove_free_edge_list(g, in_vec, eid, true);
|
|
}
|
|
}
|
|
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
static RzGraphStatus rz_graph_list_impl_del_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RZ_NULLABLE RzGraphEdgeChooser cb, void *cb_data) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphListImpl *impl = g->impl;
|
|
void **it;
|
|
|
|
rz_pvector_foreach (impl->out_edges, it) {
|
|
RzPVector *node_out_edges = *it;
|
|
if (RZ_UNLIKELY(!node_out_edges)) {
|
|
continue;
|
|
}
|
|
size_t i = 0;
|
|
while (i < rz_pvector_len(node_out_edges)) {
|
|
if (cb && !cb(rz_pvector_at(node_out_edges, i), cb_data)) {
|
|
++i;
|
|
continue;
|
|
}
|
|
rz_pvector_remove_at_unsorted(node_out_edges, i);
|
|
}
|
|
}
|
|
|
|
size_t removed = 0;
|
|
rz_pvector_foreach (impl->in_edges, it) {
|
|
RzPVector *node_in_edges = *it;
|
|
if (RZ_UNLIKELY(!node_in_edges)) {
|
|
continue;
|
|
}
|
|
size_t i = 0;
|
|
while (i < rz_pvector_len(node_in_edges)) {
|
|
RzGraphEdge *edge = rz_pvector_at(node_in_edges, i);
|
|
if (cb && !cb(edge, cb_data)) {
|
|
++i;
|
|
continue;
|
|
}
|
|
remove_free_edge_list(g, node_in_edges, i, true);
|
|
removed++;
|
|
}
|
|
}
|
|
g->n_edges -= removed;
|
|
return removed > 0 ? RZ_GRAPH_STATUS_EXISTED : RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Check if a directed edge (from -> to) exists in the adjacency list.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge exists.
|
|
* \return RZ_GRAPH_STATUS_MISSING_EDGE If edge doesn't exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
static RzGraphStatus rz_graph_list_impl_has_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
|
|
RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, from->_vec_id);
|
|
if (!out_vec || rz_pvector_empty(out_vec)) {
|
|
return RZ_GRAPH_STATUS_MISSING_EDGE;
|
|
}
|
|
|
|
bool is_exist = (edge_vec_find_eid(out_vec, from, to) != -1);
|
|
return is_exist ? RZ_GRAPH_STATUS_OK : RZ_GRAPH_STATUS_MISSING_EDGE;
|
|
}
|
|
|
|
/**
|
|
* \brief Find and return the edge (from -> to) in the adjacency list.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return the edge if found, or NULL if not found
|
|
*/
|
|
static RzGraphEdge *rz_graph_list_impl_find_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, NULL);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
|
|
RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, from->_vec_id);
|
|
if (!out_vec || rz_pvector_empty(out_vec)) {
|
|
return NULL;
|
|
}
|
|
|
|
ut64 eid = edge_vec_find_eid(out_vec, from, to);
|
|
if (eid == -1) {
|
|
return NULL;
|
|
}
|
|
return rz_pvector_at(out_vec, eid);
|
|
}
|
|
|
|
// no build-in vector iter
|
|
typedef struct {
|
|
RZ_BORROW const RzPVector /*<RzGraphEdge *>*/ *vec; // borrow
|
|
ut64 cur_id;
|
|
} RzGraphListIterState;
|
|
|
|
/**
|
|
* \brief Iterator next callback for RzPVector-backed iteration.
|
|
*
|
|
* Returns the next element in the borrowed pvector, or NULL when exhausted.
|
|
*
|
|
* \param iter the iterator
|
|
* \return next element pointer, or NULL
|
|
*/
|
|
static void *pvecotr_iter_next(RzIterator *iter) {
|
|
RzGraphListIterState *state = (RzGraphListIterState *)iter->u;
|
|
ut64 vec_size = rz_pvector_len(state->vec);
|
|
while (state->cur_id < vec_size) {
|
|
void *elem = rz_pvector_at(state->vec, state->cur_id);
|
|
state->cur_id += 1;
|
|
if (elem) {
|
|
return elem;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Wrap an RzPVector as a read-only RzIterator.
|
|
*
|
|
* The iterator borrows the vector; freeing the iterator does not
|
|
* free the underlying vector elements.
|
|
*
|
|
* \param vec the pvector to iterate over (borrowed)
|
|
* \return A new RzIterator, or NULL on failure
|
|
*/
|
|
static RZ_OWN RzIterator *pvector_as_iter(RzPVector /*<RzGraphEdge *>*/ *vec) {
|
|
if (!vec) {
|
|
return NULL;
|
|
}
|
|
|
|
// free state only, dont broke pvector nodes of graph
|
|
RzGraphListIterState *state = RZ_NEW0(RzGraphListIterState);
|
|
if (!state) {
|
|
return NULL;
|
|
}
|
|
state->cur_id = 0;
|
|
state->vec = vec;
|
|
|
|
RzIterator *iter = rz_iterator_new(
|
|
(rz_iterator_next_cb)pvecotr_iter_next,
|
|
NULL,
|
|
free,
|
|
state);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all outgoing edges of \p node (list impl).
|
|
*
|
|
* \param g The graph.
|
|
* \param node the node whose out-edges to iterate
|
|
* \return A new edge iterator owned by caller, or NULL if no out-edges
|
|
*/
|
|
static RZ_OWN RzIterator *rz_graph_list_impl_get_out_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, node->_vec_id);
|
|
if (!out_vec || rz_pvector_empty(out_vec)) {
|
|
return NULL;
|
|
}
|
|
RzIterator *iter = pvector_as_iter(out_vec);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all incoming edges of \p node (list impl).
|
|
*
|
|
* \param g The graph.
|
|
* \param node the node whose in-edges to iterate
|
|
* \return A new edge iterator owned by caller, or NULL if no in-edges
|
|
*/
|
|
static RZ_OWN RzIterator *rz_graph_list_impl_get_in_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
RzPVector /*<RzGraphEdge *>*/ *in_vec = rz_pvector_at(impl->in_edges, node->_vec_id);
|
|
if (!in_vec || rz_pvector_empty(in_vec)) {
|
|
return NULL;
|
|
}
|
|
RzIterator *iter = pvector_as_iter(in_vec);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a node to the adjacency list implementation (no-op).
|
|
*
|
|
* In the list-based implementation, nodes are managed by the graph itself.
|
|
* An orphan node simply has no edges in the edge table.
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to add
|
|
* \return always true
|
|
*/
|
|
static inline RZ_OWN bool rz_graph_list_impl_add_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, false);
|
|
// no explicit node in list-based
|
|
// all leaved to graph to manage nodes
|
|
// an orphan node will not have any edge in list edge table
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
// in_edges and out_edges are kept in sync.
|
|
size_t max_node_capacity = rz_pvector_capacity(impl->in_edges);
|
|
if (rz_pvector_len(g->node_vec) > max_node_capacity) {
|
|
rz_pvector_reserve(impl->in_edges, max_node_capacity + LIST_IMPL_DEFAULT_NODE_VEC_SIZE);
|
|
rz_pvector_reserve(impl->out_edges, max_node_capacity + LIST_IMPL_DEFAULT_NODE_VEC_SIZE);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a node from the adjacency list, removing all associated edges.
|
|
*
|
|
* First removes all outgoing edges (node -> dest), cleaning up the
|
|
* corresponding in-edges of neighbour nodes. Then removes all incoming
|
|
* edges (src -> node), cleaning up the corresponding out-edges of
|
|
* neighbour nodes. Edge user data is freed via graph callback.
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to delete
|
|
* \return true on success, false on failure
|
|
*/
|
|
static RZ_OWN bool rz_graph_list_impl_del_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, false);
|
|
RzGraphListImpl *impl = (RzGraphListImpl *)g->impl;
|
|
|
|
// remove all node -> dest
|
|
RZ_BORROW RzPVector /*<RzGraphEdge *>*/ *out_vec = rz_pvector_at(impl->out_edges, node->_vec_id);
|
|
if (out_vec && !rz_pvector_empty(out_vec)) {
|
|
ut64 i = rz_pvector_len(out_vec);
|
|
while (i-- > 0) {
|
|
RzGraphEdge *node_to_dest_as_oe = (RzGraphEdge *)rz_pvector_at(out_vec, i);
|
|
RzGraphNode *dest_node = node_to_dest_as_oe->to;
|
|
// remove related neighbour (mirror) nodes' in-edges
|
|
RzPVector /*<RzGraphEdge *>*/ *in_edges_of_dest = rz_pvector_at(impl->in_edges, dest_node->_vec_id);
|
|
if (in_edges_of_dest && !rz_pvector_empty(in_edges_of_dest)) {
|
|
// find id of node_to_dest_as_ie
|
|
ut64 eid = edge_vec_find_eid(in_edges_of_dest, node, dest_node);
|
|
if (eid != -1) {
|
|
// do not free edge data
|
|
RzGraphEdge *node_to_dest_as_ie = rz_pvector_at(in_edges_of_dest, eid);
|
|
edge_free(node_to_dest_as_ie);
|
|
rz_pvector_remove_at_unsorted(in_edges_of_dest, eid);
|
|
}
|
|
}
|
|
// Only delete don't free.
|
|
// RzGraphEdge is owned by in-vector.
|
|
rz_pvector_remove_at_unsorted(out_vec, i);
|
|
|
|
g->n_edges -= 1;
|
|
}
|
|
rz_pvector_purge(out_vec);
|
|
}
|
|
|
|
// remove all src -> node
|
|
RzPVector /*<RzGraphEdge *>*/ *in_vec = rz_pvector_at(impl->in_edges, node->_vec_id);
|
|
if (in_vec && !rz_pvector_empty(in_vec)) {
|
|
ut64 i = rz_pvector_len(in_vec);
|
|
while (i-- > 0) {
|
|
RzGraphEdge *src_to_node_as_ie = (RzGraphEdge *)rz_pvector_at(in_vec, i);
|
|
RzGraphNode *src_node = src_to_node_as_ie->from;
|
|
// remove related neighbour (mirror) nodes' out-edges
|
|
RzPVector /*<RzGraphEdge *>*/ *out_edges_of_src = rz_pvector_at(impl->out_edges, src_node->_vec_id);
|
|
if (out_edges_of_src) {
|
|
// find src_to_node_as_oe in out edge vec of src node
|
|
ut64 eid = edge_vec_find_eid(out_edges_of_src, src_node, node);
|
|
if (eid != -1) {
|
|
// Only delete don't free.
|
|
// RzGraphEdge is owned by in-vector.
|
|
rz_pvector_remove_at_unsorted(out_edges_of_src, eid);
|
|
}
|
|
}
|
|
|
|
// free struct
|
|
// NOTE: reverse iteration allows safe rz_pvector_remove_at —
|
|
// removing from the tail does not shift earlier indices.
|
|
edge_free(src_to_node_as_ie);
|
|
rz_pvector_remove_at_unsorted(in_vec, i);
|
|
g->n_edges -= 1;
|
|
}
|
|
rz_pvector_purge(in_vec);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Finalize and free the adjacency list implementation.
|
|
*
|
|
* Frees both the in_edges and out_edges hash tables and the impl struct itself.
|
|
*
|
|
* \param impl the list impl to finalize
|
|
*/
|
|
static void rz_graph_list_impl_fini(void *impl) {
|
|
if (!impl) {
|
|
return;
|
|
}
|
|
|
|
RzGraphListImpl *list_impl = impl;
|
|
rz_pvector_free(list_impl->out_edges);
|
|
rz_pvector_free(list_impl->in_edges);
|
|
free(list_impl);
|
|
}
|
|
|
|
/**
|
|
* \brief HtUP value free callback to free an edge vector.
|
|
*
|
|
* Registered as ht_up free callback, called when a hash table entry
|
|
* holding an edge RzPVector is removed.
|
|
*
|
|
* \param value the RzPVector to free
|
|
*/
|
|
static void edge_vec_free_cb(void *value) {
|
|
RzPVector /*<RzGraphEdge *>*/ *vec = (RzPVector /*<RzGraphEdge *>*/ *)value;
|
|
rz_pvector_free(vec);
|
|
}
|
|
|
|
/**
|
|
* \brief Initialize a new adjacency list graph implementation.
|
|
*
|
|
* Allocates and sets up in_edges and out_edges hash tables.
|
|
*
|
|
* \return A new RzGraphListImpl, or NULL on failure
|
|
*/
|
|
static RzGraphListImpl *rz_graph_list_impl_init(void) {
|
|
RzGraphListImpl *impl = RZ_NEW0(RzGraphListImpl);
|
|
if (!impl) {
|
|
return NULL;
|
|
}
|
|
impl->out_edges = rz_pvector_new(edge_vec_free_cb);
|
|
impl->in_edges = rz_pvector_new(edge_vec_free_cb);
|
|
rz_pvector_reserve(impl->in_edges, LIST_IMPL_DEFAULT_NODE_VEC_SIZE);
|
|
rz_pvector_reserve(impl->out_edges, LIST_IMPL_DEFAULT_NODE_VEC_SIZE);
|
|
|
|
if (!impl->out_edges || !impl->in_edges) {
|
|
rz_graph_list_impl_fini(impl);
|
|
return NULL;
|
|
}
|
|
return impl;
|
|
}
|
|
|
|
/* Adjacency Matrix Impl */
|
|
/**
|
|
* -------------------------
|
|
* -> | to1 | to2 | to3 | ... |
|
|
* -> from1 | | | | ... |
|
|
* -> from2 | | | | ... |
|
|
* -> from3 | | | | ... |
|
|
* ......
|
|
* -------------------------
|
|
*/
|
|
|
|
static inline ut64 rz_graph_matrix_impl_mem_usage(const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
return impl->capacity * impl->capacity * sizeof(RzGraphEdge *);
|
|
}
|
|
|
|
/**
|
|
* \brief Get a pointer to the matrix cell for edge (from -> to).
|
|
*
|
|
* \param impl matrix impl
|
|
* \param from_vec_id vec id of source node
|
|
* \param to_vec_id vec id of destination node
|
|
* \return pointer to the RzGraphEdgeNew* cell in the matrix
|
|
*/
|
|
static inline RzGraphEdge **matrix_cell(const RzGraphMatrixImpl *impl, ut64 from_vec_id, ut64 to_vec_id) {
|
|
return &(impl->matrix[from_vec_id * impl->capacity + to_vec_id]);
|
|
}
|
|
|
|
/**
|
|
* \brief Grow the adjacency matrix if needed to hold at least \p required nodes.
|
|
*
|
|
* If the current capacity is sufficient, returns immediately. Otherwise
|
|
* allocates a larger matrix and copies the old data over.
|
|
*
|
|
* \param impl matrix impl
|
|
* \param required the minimum capacity needed
|
|
* \return true on success, false on allocation failure
|
|
*/
|
|
static bool rz_graph_matrix_impl_require_capacity(RzGraphMatrixImpl *impl, ut64 required) {
|
|
if (required <= impl->capacity) {
|
|
return true;
|
|
}
|
|
ut64 new_cap = impl->capacity;
|
|
while (new_cap < required) {
|
|
new_cap += new_cap / 4;
|
|
}
|
|
|
|
RzGraphEdge **new_matrix = RZ_NEWS0(RzGraphEdge *, new_cap * new_cap);
|
|
if (!new_matrix) {
|
|
RZ_LOG_WARN("Failed to adjust matrix capacity to %" PFMT64u "\n", new_cap);
|
|
return false;
|
|
}
|
|
|
|
// move old to new matrix
|
|
for (ut32 r = 0; r < impl->capacity; ++r) {
|
|
for (ut32 c = 0; c < impl->capacity; ++c) {
|
|
new_matrix[r * new_cap + c] = impl->matrix[r * impl->capacity + c];
|
|
}
|
|
}
|
|
|
|
free(impl->matrix);
|
|
impl->matrix = new_matrix;
|
|
impl->capacity = new_cap;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a directed edge (from -> to) in the matrix implementation.
|
|
*
|
|
* Sets the matrix cell at [from._vec_id][to._vec_id]. Fails if the edge already exists.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \param edge_data user data attached to the edge
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge was added.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If edge existed.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
static RzGraphStatus rz_graph_matrix_impl_add_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to, RZ_OWN void *edge_data) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphMatrixImpl *impl = (RzGraphMatrixImpl *)g->impl;
|
|
RzGraphEdge **cell = matrix_cell(impl, from->_vec_id, to->_vec_id);
|
|
if (*cell) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(edge_data);
|
|
}
|
|
// already exist edge
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
RzGraphEdge *e = edge_new(from, to, edge_data);
|
|
if (!e) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(edge_data);
|
|
}
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
*cell = e;
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
static RzGraphStatus rz_graph_matrix_impl_del_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RZ_NULLABLE RzGraphEdgeChooser cb, void *cb_data) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
size_t removed = 0;
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
for (size_t i = 0; i < rz_pvector_len(g->node_vec); ++i) {
|
|
for (size_t j = 0; j < rz_pvector_len(g->node_vec); ++j) {
|
|
RzGraphEdge **cell = matrix_cell(impl, i, j);
|
|
if (!*cell || (cb && !cb(*cell, cb_data))) {
|
|
continue;
|
|
}
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free((*cell)->data);
|
|
}
|
|
edge_free(*cell);
|
|
*cell = NULL;
|
|
removed++;
|
|
}
|
|
}
|
|
g->n_edges -= removed;
|
|
return removed > 0 ? RZ_GRAPH_STATUS_EXISTED : RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a directed edge (from -> to) in the matrix implementation.
|
|
*
|
|
* Clears the matrix cell and frees edge user data via graph callback.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return true on success, false if no such edge
|
|
*/
|
|
static RzGraphStatus rz_graph_matrix_impl_del_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphMatrixImpl *impl = (RzGraphMatrixImpl *)g->impl;
|
|
RzGraphEdge **cell = matrix_cell(impl, from->_vec_id, to->_vec_id);
|
|
if (!*cell) {
|
|
// no such edge
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
// free user data
|
|
if (g->edge_data_free && (*cell)->data) {
|
|
g->edge_data_free((*cell)->data);
|
|
}
|
|
|
|
edge_free(*cell);
|
|
*cell = NULL;
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
/**
|
|
* \brief Check if a directed edge (from -> to) exists in the matrix.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge exists.
|
|
* \return RZ_GRAPH_STATUS_MISSING_EDGE If edge doesn't exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
static RzGraphStatus rz_graph_matrix_impl_has_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
return *matrix_cell(impl, from->_vec_id, to->_vec_id) != NULL ? RZ_GRAPH_STATUS_OK : RZ_GRAPH_STATUS_MISSING_EDGE;
|
|
}
|
|
|
|
/**
|
|
* \brief Find and return the edge (from -> to) in the matrix.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return the edge if found, or NULL
|
|
*/
|
|
static RzGraphEdge *rz_graph_matrix_find_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, NULL);
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
return *matrix_cell(impl, from->_vec_id, to->_vec_id);
|
|
}
|
|
|
|
typedef struct {
|
|
RZ_BORROW const RzGraph /*<NodeType *, EdgeType *>*/ *g;
|
|
ut64 node_vid;
|
|
ut32 cur;
|
|
bool scan_out; // true to scan out edge or in edge
|
|
} RzGraphMatrixIterState;
|
|
|
|
/**
|
|
* \brief Iterator next callback for matrix edge iteration.
|
|
*
|
|
* Scans the matrix row (out-edges) or column (in-edges) for the given
|
|
* node, skipping deleted (NULL) nodes.
|
|
*
|
|
* \param it the iterator
|
|
* \return next RzGraphEdgeNew* or NULL when exhausted
|
|
*/
|
|
static void *matrix_edge_iter_next(RzIterator *it) {
|
|
RzGraphMatrixIterState *state = (RzGraphMatrixIterState *)it->u;
|
|
ut64 n = rz_pvector_len(state->g->node_vec);
|
|
|
|
while (state->cur < n) {
|
|
ut64 cur = state->cur;
|
|
// check if be deleted, should skip deleted holes
|
|
RzGraphNode *candidate_node = rz_pvector_at(state->g->node_vec, cur);
|
|
if (!candidate_node) {
|
|
state->cur += 1;
|
|
continue;
|
|
}
|
|
|
|
RzGraphEdge *e;
|
|
if (state->scan_out) {
|
|
e = *matrix_cell((RzGraphMatrixImpl *)state->g->impl, state->node_vid, cur);
|
|
} else {
|
|
e = *matrix_cell((RzGraphMatrixImpl *)state->g->impl, cur, state->node_vid);
|
|
}
|
|
|
|
if (e) {
|
|
state->cur += 1;
|
|
return e;
|
|
}
|
|
|
|
state->cur += 1;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Create a matrix edge iterator for a given node.
|
|
*
|
|
* Wraps the matrix scan logic into an RzIterator. The iterator scans
|
|
* either a row (out-edges) or a column (in-edges) of the matrix.
|
|
*
|
|
* \param g graph (borrowed)
|
|
* \param node_vid vec id of the node
|
|
* \param scan_out true for outgoing edges, false for incoming edges
|
|
* \return A new RzIterator, or NULL on failure
|
|
*/
|
|
static RzIterator *matrix_edge_as_iter(const RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 node_vid, bool scan_out) {
|
|
RzGraphMatrixIterState *state = RZ_NEW0(RzGraphMatrixIterState);
|
|
if (!state) {
|
|
return NULL;
|
|
}
|
|
state->g = g;
|
|
state->node_vid = node_vid;
|
|
state->cur = 0;
|
|
state->scan_out = scan_out;
|
|
|
|
RzIterator *iter = rz_iterator_new(
|
|
(rz_iterator_next_cb)matrix_edge_iter_next,
|
|
NULL,
|
|
free,
|
|
state);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all incoming edges of \p node (matrix impl).
|
|
*
|
|
* \param g The graph.
|
|
* \param node the node whose in-edges to iterate
|
|
* \return A new edge iterator, or NULL if node is NULL
|
|
*/
|
|
static RzIterator *rz_graph_matrix_impl_get_in_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return matrix_edge_as_iter(g, node->_vec_id, false);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all outgoing edges of \p node (matrix impl).
|
|
*
|
|
* \param g The graph.
|
|
* \param node the node whose out-edges to iterate
|
|
* \return A new edge iterator, or NULL if node is NULL
|
|
*/
|
|
static RzIterator *rz_graph_matrix_impl_get_out_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return matrix_edge_as_iter(g, node->_vec_id, true);
|
|
}
|
|
|
|
/**
|
|
* \brief Add a node to the matrix implementation.
|
|
*
|
|
* Ensures the matrix has enough capacity to hold the new node's vec id.
|
|
* May trigger a matrix resize.
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to add
|
|
* \return true on success, false if capacity growth fails
|
|
*/
|
|
static bool rz_graph_matrix_impl_add_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, false);
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
if (!rz_graph_matrix_impl_require_capacity(impl, node->_vec_id + 1)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a node from the matrix, clearing all associated edges.
|
|
*
|
|
* Zeroes out the entire row (out-edges) and column (in-edges) for \p node,
|
|
* freeing edge user data and edge structs along the way.
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to delete
|
|
* \return true on success, false on failure
|
|
*/
|
|
static bool rz_graph_matrix_impl_del_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, false);
|
|
RzGraphMatrixImpl *impl = g->impl;
|
|
ut64 vec_id = node->_vec_id;
|
|
ut64 n = rz_pvector_len(g->node_vec);
|
|
|
|
// remove all related out edges -> matrix[vec_id][j]
|
|
for (ut64 c = 0; c < n; ++c) {
|
|
RzGraphEdge **cell = matrix_cell(impl, vec_id, c);
|
|
if (*cell) {
|
|
if (g->edge_data_free && (*cell)->data) {
|
|
g->edge_data_free((*cell)->data);
|
|
}
|
|
edge_free(*cell);
|
|
*cell = NULL;
|
|
g->n_edges -= 1;
|
|
}
|
|
}
|
|
|
|
// remove all related in edges -> matrix[i][vec_id]
|
|
for (ut64 r = 0; r < n; ++r) {
|
|
RzGraphEdge **cell = matrix_cell(impl, r, vec_id);
|
|
if (*cell) {
|
|
if (g->edge_data_free && (*cell)->data) {
|
|
g->edge_data_free((*cell)->data);
|
|
}
|
|
edge_free(*cell);
|
|
*cell = NULL;
|
|
g->n_edges -= 1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Finalize and free the adjacency matrix implementation.
|
|
*
|
|
* Frees all remaining edge structs in the matrix, then the matrix array
|
|
* and the impl struct itself.
|
|
*
|
|
* \param impl the matrix impl to finalize
|
|
*/
|
|
static void rz_matrix_fini(void *impl) {
|
|
if (!impl) {
|
|
return;
|
|
}
|
|
RzGraphMatrixImpl *matrix_impl = (RzGraphMatrixImpl *)impl;
|
|
for (ut64 i = 0; i < matrix_impl->capacity * matrix_impl->capacity; ++i) {
|
|
if (matrix_impl->matrix[i]) {
|
|
free(matrix_impl->matrix[i]);
|
|
matrix_impl->matrix[i] = NULL;
|
|
}
|
|
}
|
|
free(matrix_impl->matrix);
|
|
free(matrix_impl);
|
|
}
|
|
|
|
/**
|
|
* \brief Initialize a new adjacency matrix graph implementation.
|
|
*
|
|
* Allocates the matrix with the given \p capacity. Falls back to
|
|
* MATRIX_DEFAULT_CAPACITY if capacity is 0.
|
|
*
|
|
* \param capacity initial matrix dimension (number of nodes)
|
|
* \return A new RzGraphMatrixImpl, or NULL on failure
|
|
*/
|
|
static RzGraphMatrixImpl *rz_graph_matrix_impl_init(ut64 capacity) {
|
|
RzGraphMatrixImpl *impl = RZ_NEW0(RzGraphMatrixImpl);
|
|
if (!impl) {
|
|
return NULL;
|
|
}
|
|
impl->capacity = capacity ? capacity : RZ_GRAPH_MATRIX_DEFAULT_CAPACITY;
|
|
impl->matrix = RZ_NEWS0(RzGraphEdge *, impl->capacity * impl->capacity);
|
|
if (!impl->matrix) {
|
|
RZ_LOG_WARN("Failed to init graph matrix with capacity %" PFMT64u "\n", impl->capacity)
|
|
rz_matrix_fini(impl);
|
|
return NULL;
|
|
}
|
|
return impl;
|
|
}
|
|
|
|
/* register operation */
|
|
static const RzGraphImplOps list_impl_ops = {
|
|
.add_edge = rz_graph_list_impl_add_edge,
|
|
.del_edge = rz_graph_list_impl_del_edge,
|
|
.del_edges = rz_graph_list_impl_del_edges,
|
|
.has_edge = rz_graph_list_impl_has_edge,
|
|
.find_edge = rz_graph_list_impl_find_edge,
|
|
.get_out_edges = rz_graph_list_impl_get_out_edges,
|
|
.get_in_edges = rz_graph_list_impl_get_in_edges,
|
|
.add_node = rz_graph_list_impl_add_node,
|
|
.del_node = rz_graph_list_impl_del_node,
|
|
.fini = rz_graph_list_impl_fini
|
|
};
|
|
|
|
static const RzGraphImplOps matrix_impl_ops = {
|
|
.add_edge = rz_graph_matrix_impl_add_edge,
|
|
.del_edge = rz_graph_matrix_impl_del_edge,
|
|
.del_edges = rz_graph_matrix_impl_del_edges,
|
|
.has_edge = rz_graph_matrix_impl_has_edge,
|
|
.find_edge = rz_graph_matrix_find_edge,
|
|
.get_out_edges = rz_graph_matrix_impl_get_out_edges,
|
|
.get_in_edges = rz_graph_matrix_impl_get_in_edges,
|
|
.add_node = rz_graph_matrix_impl_add_node,
|
|
.del_node = rz_graph_matrix_impl_del_node,
|
|
.mem_usage = rz_graph_matrix_impl_mem_usage,
|
|
.fini = rz_matrix_fini
|
|
};
|
|
|
|
/* RZ_API Graph Operations */
|
|
|
|
/**
|
|
* \brief Default hash function for node data.
|
|
*
|
|
* Simply casts the pointer to ut64 as the hash value.
|
|
*
|
|
* \param node_data The node data.
|
|
* \return hash value
|
|
*/
|
|
static ut64 rz_graph_node_default_hash(const void *node_data) {
|
|
return (ut64)(uintptr_t)node_data;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the number nodes the graph has.
|
|
*/
|
|
RZ_API ut64 rz_graph_get_n_nodes(RZ_NONNULL const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, 0);
|
|
return g->n_nodes;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the number edges the graph has.
|
|
*/
|
|
RZ_API ut64 rz_graph_get_n_edges(RZ_NONNULL const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, 0);
|
|
return g->n_edges;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the implementation type of the graph. Or INT_MAX in case of failure.
|
|
*/
|
|
RZ_API RzGraphImplType rz_graph_get_impl_type(RZ_NONNULL const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, INT_MAX);
|
|
return g->impl_type;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the data pointer of the node.
|
|
*/
|
|
RZ_API const void *rz_graph_node_get_data(RZ_NONNULL const RzGraphNode *node) {
|
|
rz_return_val_if_fail(node, NULL);
|
|
return node->data;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the mutable data pointer of the node.
|
|
*/
|
|
RZ_API RZ_BORROW void *rz_graph_node_get_data_mut(RZ_NONNULL RZ_BORROW RzGraphNode *node) {
|
|
rz_return_val_if_fail(node, NULL);
|
|
return node->data;
|
|
}
|
|
|
|
/**
|
|
* \brief Set the data pointer of the edge.
|
|
*/
|
|
RZ_API void rz_graph_edge_set_data(RZ_NONNULL RZ_BORROW RzGraphEdge *edge, RZ_NULLABLE RZ_OWN void *data) {
|
|
rz_return_if_fail(edge);
|
|
edge->data = data;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the data pointer of the edge.
|
|
*/
|
|
RZ_API const void *rz_graph_edge_get_data(RZ_NONNULL const RzGraphEdge *edge) {
|
|
rz_return_val_if_fail(edge, NULL);
|
|
return edge->data;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the mutable data pointer of the edge.
|
|
*/
|
|
RZ_API RZ_BORROW void *rz_graph_edge_get_data_mut(RZ_NONNULL RZ_BORROW RzGraphEdge *edge) {
|
|
rz_return_val_if_fail(edge, NULL);
|
|
return edge->data;
|
|
}
|
|
|
|
RZ_API const RzGraphNode *rz_graph_edge_get_from(RZ_NONNULL const RzGraphEdge *edge) {
|
|
rz_return_val_if_fail(edge, NULL);
|
|
return edge->from;
|
|
}
|
|
|
|
RZ_API const RzGraphNode *rz_graph_edge_get_to(RZ_NONNULL const RzGraphEdge *edge) {
|
|
rz_return_val_if_fail(edge, NULL);
|
|
return edge->to;
|
|
}
|
|
|
|
/**
|
|
* \brief Create a new RzGraphNew with the specified implementation type.
|
|
*
|
|
* Initializes a graph with either an adjacency list or adjacency matrix
|
|
* backend. Sets up the node hash table, node vector, and dispatches
|
|
* to the appropriate impl initializer.
|
|
*
|
|
* \param impl_type RZ_GRAPH_IMPL_LIST or RZ_GRAPH_IMPL_MATRIX
|
|
* \param id_hash_fcn Hash function to generate the unique id for a node.
|
|
* If it is NULL, then the graph will use the node data pointers as hash ids.
|
|
*
|
|
* In the common case that the nodes should be identified by integers and have no data at all,
|
|
* the user must initialize the graph with id_hash_fcn == NULL.
|
|
* Then pass `RZ_GRAPH_INT_AS_DATA(<node_int_id>)` to the `const void *identifier` parameter of API functions.
|
|
*
|
|
* If no identifiers are needed, initialize the graph with id_hash_fcn == NULL,
|
|
* and use the functions taking node pointers from here on.
|
|
*
|
|
* \param node_free callback to free node user data, or NULL
|
|
* \param edge_free callback to free edge user data, or NULL
|
|
* \return A new RzGraphNew, or NULL on failure.
|
|
*/
|
|
RZ_API RZ_OWN RzGraph /*<NodeType *, EdgeType *>*/ *rz_graph_new(
|
|
RzGraphImplType impl_type,
|
|
RZ_NULLABLE RzGraphIdentifierHash id_hash_fcn,
|
|
RzGraphNodeDataFree node_free,
|
|
RzGraphEdgeDataFree edge_free) {
|
|
RzGraph /*<NodeType *, EdgeType *>*/ *g = RZ_NEW0(RzGraph);
|
|
if (!g) {
|
|
return NULL;
|
|
}
|
|
|
|
// unable to wrap node_data_free as node_free here
|
|
// free by iterates g->node_vec
|
|
g->nodes = ht_up_new(NULL, NULL);
|
|
if (!g->nodes) {
|
|
free(g);
|
|
return NULL;
|
|
}
|
|
|
|
// reference of g->nodes, but ordered
|
|
g->free_vec_ids = rz_vector_new(sizeof(size_t), NULL, NULL);
|
|
g->node_vec = rz_pvector_new(NULL);
|
|
if (!g->node_vec || !g->free_vec_ids) {
|
|
rz_vector_free(g->free_vec_ids);
|
|
rz_pvector_free(g->node_vec);
|
|
ht_up_free(g->nodes);
|
|
free(g);
|
|
return NULL;
|
|
}
|
|
|
|
// use default hash if hash is NULL
|
|
if (!id_hash_fcn) {
|
|
id_hash_fcn = rz_graph_node_default_hash;
|
|
}
|
|
|
|
g->hash_func = id_hash_fcn;
|
|
g->node_data_free = node_free;
|
|
g->edge_data_free = edge_free;
|
|
g->impl_type = impl_type;
|
|
|
|
switch (impl_type) {
|
|
case RZ_GRAPH_IMPL_LIST: {
|
|
RzGraphListImpl *impl = rz_graph_list_impl_init();
|
|
if (!impl) {
|
|
goto fail_clean;
|
|
}
|
|
|
|
g->impl_ops = &list_impl_ops;
|
|
g->impl = impl;
|
|
return g;
|
|
}
|
|
case RZ_GRAPH_IMPL_MATRIX: {
|
|
RzGraphMatrixImpl *impl = rz_graph_matrix_impl_init(RZ_GRAPH_MATRIX_DEFAULT_CAPACITY);
|
|
if (!impl) {
|
|
goto fail_clean;
|
|
}
|
|
g->impl_ops = &matrix_impl_ops;
|
|
g->impl = impl;
|
|
return g;
|
|
}
|
|
default:
|
|
goto fail_clean;
|
|
}
|
|
fail_clean:
|
|
rz_vector_free(g->free_vec_ids);
|
|
rz_pvector_free(g->node_vec);
|
|
ht_up_free(g->nodes);
|
|
free(g);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Free an RzGraphNew and all its contents.
|
|
*
|
|
* Finalizes the impl backend, frees all node user data via the registered
|
|
* callback, and releases all internal containers.
|
|
*
|
|
* \param g graph to free, or NULL (no-op)
|
|
*/
|
|
RZ_API void rz_graph_free(RZ_NULLABLE RZ_OWN RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
if (!g) {
|
|
return;
|
|
}
|
|
|
|
// free edge user data before destroying the impl
|
|
if (g->edge_data_free && g->impl_ops && g->impl && g->node_vec) {
|
|
void **nit;
|
|
rz_pvector_foreach (g->node_vec, nit) {
|
|
RzGraphNode *node = (RzGraphNode *)(*nit);
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
RzIterator *edge_it = g->impl_ops->get_out_edges(g, node);
|
|
if (!edge_it) {
|
|
continue;
|
|
}
|
|
RzGraphEdge *e;
|
|
rz_iterator_foreach(edge_it, e) {
|
|
if (e->data) {
|
|
g->edge_data_free(e->data);
|
|
e->data = NULL;
|
|
}
|
|
}
|
|
rz_iterator_free(edge_it);
|
|
}
|
|
}
|
|
|
|
// cleaned inner impl data
|
|
if (g->impl_ops && g->impl) {
|
|
g->impl_ops->fini(g->impl);
|
|
}
|
|
|
|
// clean user data of all nodes
|
|
if (g->nodes && g->node_vec) {
|
|
void **it;
|
|
rz_pvector_foreach (g->node_vec, it) {
|
|
RzGraphNode *node = (RzGraphNode *)(*it);
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
if (g->node_data_free && node->data) {
|
|
g->node_data_free(node->data);
|
|
node->data = NULL;
|
|
}
|
|
free(node);
|
|
*it = NULL;
|
|
}
|
|
}
|
|
|
|
// clean hash table container since all nodes data has been cleaned
|
|
ht_up_free(g->nodes);
|
|
g->nodes = NULL;
|
|
|
|
// clean node_vec container
|
|
if (g->node_vec) {
|
|
rz_pvector_free(g->node_vec);
|
|
g->node_vec = NULL;
|
|
}
|
|
if (g->free_vec_ids) {
|
|
rz_vector_free(g->free_vec_ids);
|
|
g->free_vec_ids = NULL;
|
|
}
|
|
|
|
free(g);
|
|
}
|
|
|
|
/**
|
|
* \brief Reset graph to an empty state, preserving its configuration.
|
|
*
|
|
* Finalizes the current impl, frees all nodes and edges, then
|
|
* re-initializes the impl backend with a fresh state.
|
|
*
|
|
* \param g graph to reset
|
|
*/
|
|
RZ_API void rz_graph_reset(RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_if_fail(g);
|
|
|
|
// free edge user data before destroying the impl
|
|
if (g->edge_data_free && g->impl_ops && g->impl && g->node_vec) {
|
|
void **nit;
|
|
rz_pvector_foreach (g->node_vec, nit) {
|
|
RzGraphNode *node = (RzGraphNode *)(*nit);
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
RzIterator *edge_it = g->impl_ops->get_out_edges(g, node);
|
|
if (!edge_it) {
|
|
continue;
|
|
}
|
|
RzGraphEdge *e;
|
|
rz_iterator_foreach(edge_it, e) {
|
|
if (e->data) {
|
|
g->edge_data_free(e->data);
|
|
e->data = NULL;
|
|
}
|
|
}
|
|
rz_iterator_free(edge_it);
|
|
}
|
|
}
|
|
|
|
if (g->impl_ops && g->impl) {
|
|
g->impl_ops->fini(g->impl);
|
|
g->impl = NULL;
|
|
}
|
|
|
|
// free node and data
|
|
void **it;
|
|
|
|
// free nodes to fix leak
|
|
rz_pvector_foreach (g->node_vec, it) {
|
|
RzGraphNode *node = (RzGraphNode *)(*it);
|
|
if (node) {
|
|
if (g->node_data_free && node->data) {
|
|
g->node_data_free(node->data);
|
|
}
|
|
free(node);
|
|
}
|
|
}
|
|
|
|
// clean all nodes
|
|
if (g->nodes) {
|
|
ht_up_free(g->nodes);
|
|
g->nodes = NULL;
|
|
}
|
|
|
|
// clean reference
|
|
if (g->node_vec) {
|
|
rz_pvector_free(g->node_vec);
|
|
g->node_vec = NULL;
|
|
}
|
|
if (g->free_vec_ids) {
|
|
rz_vector_free(g->free_vec_ids);
|
|
}
|
|
|
|
// re-init
|
|
g->nodes = ht_up_new(NULL, NULL);
|
|
g->n_nodes = 0;
|
|
g->n_edges = 0;
|
|
g->node_vec = rz_pvector_new(NULL);
|
|
g->free_vec_ids = rz_vector_new(sizeof(size_t), NULL, NULL);
|
|
|
|
switch (g->impl_type) {
|
|
case RZ_GRAPH_IMPL_LIST:
|
|
g->impl = rz_graph_list_impl_init();
|
|
if (!g->impl) {
|
|
RZ_LOG_WARN("Failed to reset, clear data only\n");
|
|
return;
|
|
}
|
|
break;
|
|
case RZ_GRAPH_IMPL_MATRIX:
|
|
g->impl = rz_graph_matrix_impl_init(RZ_GRAPH_MATRIX_DEFAULT_CAPACITY);
|
|
if (!g->impl) {
|
|
RZ_LOG_WARN("Failed to reset, clear data only\n");
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
RZ_LOG_WARN("Unknown graph impl type %d, failed to reset, clear data only\n", g->impl_type);
|
|
}
|
|
}
|
|
|
|
static RzGraphStatus internal_add(RzGraph /*<NodeType *, EdgeType *>*/ *g, RZ_OWN void *node_data, ut64 hash_id, RzGraphNode **out_ptr) {
|
|
RzGraphNode *node = RZ_NEW0(RzGraphNode);
|
|
if (!node) {
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
node->hash_id = hash_id;
|
|
node->data = node_data;
|
|
|
|
// insert node into hash table
|
|
if (!ht_up_insert(g->nodes, hash_id, node)) {
|
|
if (g->node_data_free && node->data) {
|
|
g->node_data_free(node->data);
|
|
}
|
|
node->data = NULL;
|
|
free(node);
|
|
if (out_ptr) {
|
|
*out_ptr = ht_up_find(g->nodes, hash_id, NULL);
|
|
}
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
// push node reference into vec and update.
|
|
// g->nodes_vec must be updated before calling the implementation specific function.
|
|
// The vector length is the source of maximum nodes in the graph.
|
|
// Implementation specifics might need it.
|
|
bool used_free_slot = false;
|
|
if (rz_vector_len(g->free_vec_ids) > 0) {
|
|
rz_vector_pop_front(g->free_vec_ids, &node->_vec_id);
|
|
rz_pvector_assign_at(g->node_vec, node->_vec_id, node);
|
|
used_free_slot = true;
|
|
} else {
|
|
node->_vec_id = rz_pvector_len(g->node_vec);
|
|
rz_pvector_push(g->node_vec, node);
|
|
}
|
|
|
|
if (!g->impl_ops->add_node(g, node)) {
|
|
// revert if failed
|
|
if (used_free_slot) {
|
|
rz_vector_push(g->free_vec_ids, &node->_vec_id);
|
|
rz_pvector_assign_at(g->node_vec, node->_vec_id, NULL);
|
|
} else {
|
|
rz_pvector_pop(g->node_vec);
|
|
}
|
|
ht_up_delete(g->nodes, hash_id);
|
|
free(node);
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
|
|
// good
|
|
g->n_nodes += 1;
|
|
if (out_ptr) {
|
|
*out_ptr = node;
|
|
}
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a new node with user data to the graph.
|
|
* It hashes the \p node_data to create a unique node id and inserts it into the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \param node_data Data attached to the node. NULL is considered valid data!
|
|
* \param node_ptr The pointer to the node.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If node was added.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If node existed.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error. node_ptr won't be modified in this case.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_add_node(
|
|
RzGraph /*<NodeType *, EdgeType *>*/ *g,
|
|
RZ_NULLABLE RZ_OWN void *node_data,
|
|
RZ_OUT RZ_NULLABLE RZ_BORROW RzGraphNode **node_ptr) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
ut64 hash_id = g->hash_func(node_data);
|
|
return internal_add(g, node_data, hash_id, node_ptr);
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a node from the graph, removing all associated edges.
|
|
*
|
|
* Dispatches to the impl backend to clean up edges, then removes the
|
|
* node from the hash table and node vector, and frees node user data.
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to delete (ownership transferred)
|
|
*
|
|
* \return RZ_GRAPH_STATUS_EXISTED If node existed and was deleted.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_del_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, RZ_OWN RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, RZ_GRAPH_STATUS_ERR);
|
|
// dispatch to impl to maintain edge data struct if needed
|
|
if (!g->impl_ops->del_node(g, node)) {
|
|
RZ_LOG_WARN("Impl failed to delete node, failed to delete\n");
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
|
|
// remove from hash table
|
|
ht_up_delete(g->nodes, node->hash_id);
|
|
|
|
// set node reference as NULL
|
|
rz_pvector_set(g->node_vec, node->_vec_id, NULL);
|
|
rz_vector_push(g->free_vec_ids, &node->_vec_id);
|
|
|
|
// clean user data
|
|
if (g->node_data_free && node->data) {
|
|
g->node_data_free(node->data);
|
|
node->data = NULL;
|
|
}
|
|
free(node);
|
|
|
|
g->n_nodes -= 1;
|
|
return RZ_GRAPH_STATUS_EXISTED;
|
|
}
|
|
|
|
/**
|
|
* \brief Find a node in the graph by its identifier.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
* \return the node if found (borrowed), or NULL
|
|
*/
|
|
RZ_API RZ_BORROW RzGraphNode *rz_graph_find_node(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
return ht_up_find(g->nodes, hash_id, NULL);
|
|
}
|
|
|
|
/**
|
|
* \brief Add a directed edge between two nodes.
|
|
*
|
|
* Dispatches to the impl backend to create the edge.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \param edge_data user data attached to the edge
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge was added.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If edge existed.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_add_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to, RZ_OWN void *edge_data) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphStatus s = g->impl_ops->add_edge(g, from, to, edge_data);
|
|
if (s == RZ_GRAPH_STATUS_OK) {
|
|
g->n_edges += 1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* \brief Updates a directed edge between two nodes with the given \p edge_data.
|
|
* If the edge doesn't exist it creates it.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \param edge_data user data attached to the edge
|
|
* \param cb An optional callback which returns true if the edge should be updated, and false if it shouldn't.
|
|
* \param cb_data The callback data.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If there was no edge and a new one was added.
|
|
* \return RZ_GRAPH_STATUS_MISSING_NODE If there was no edge and but no edge was added,
|
|
* because either of the nodes does not exist.
|
|
* \return RZ_GRAPH_STATUS_UPDATED If an existing edge was updated.
|
|
* \return RZ_GRAPH_STATUS_NOT_UPDATED If an existing edge was not updated.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_update_edge(
|
|
RZ_NONNULL RZ_BORROW RzGraph /*<NodeType *, EdgeType *>*/ *g,
|
|
RZ_NONNULL RZ_OWN RzGraphNode *from,
|
|
RZ_NONNULL RZ_OWN RzGraphNode *to,
|
|
RZ_NULLABLE RZ_OWN void *edge_data,
|
|
RZ_NULLABLE RzGraphEdgeChooser cb,
|
|
void *cb_data) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphEdge *e = rz_graph_find_edge(g, from, to);
|
|
bool update = (!cb || (e && cb(e, cb_data)));
|
|
if (e && update) {
|
|
if (g->edge_data_free) {
|
|
g->edge_data_free(e->data);
|
|
}
|
|
e->data = edge_data;
|
|
return RZ_GRAPH_STATUS_UPDATED;
|
|
} else if (e && !update) {
|
|
return RZ_GRAPH_STATUS_NOT_UPDATED;
|
|
} else if (!e) {
|
|
RzGraphStatus s = g->impl_ops->add_edge(g, from, to, edge_data);
|
|
// Edge is newly added.
|
|
if (s == RZ_GRAPH_STATUS_OK) {
|
|
g->n_edges += 1;
|
|
}
|
|
return s == RZ_GRAPH_STATUS_ERR ? RZ_GRAPH_STATUS_ERR : RZ_GRAPH_STATUS_OK;
|
|
}
|
|
return RZ_GRAPH_STATUS_ERR;
|
|
}
|
|
|
|
/**
|
|
* \brief Updates a directed edge between two nodes with the given \p edge_data.
|
|
* If the edge doesn't exist it creates it.
|
|
*
|
|
* \param g The graph.
|
|
* \param from_id Source node id.
|
|
* \param to_id Destination node id.
|
|
* \param edge_data user data attached to the edge
|
|
* \param cb An optional callback which returns true if the edge should be updated, and false if it shouldn't.
|
|
* \param cb_data The callback data.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If there was no edge and a new one was added.
|
|
* \return RZ_GRAPH_STATUS_MISSING_NODE If there was no edge and but no edge was added,
|
|
* because either of the nodes does not exist.
|
|
* \return RZ_GRAPH_STATUS_UPDATED If an existing edge was updated.
|
|
* \return RZ_GRAPH_STATUS_NOT_UPDATED If an existing edge was not updated.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_update_edge_by_id(
|
|
RZ_NONNULL RZ_BORROW RzGraph /*<NodeType *, EdgeType *>*/ *g,
|
|
ut64 from_id,
|
|
ut64 to_id,
|
|
RZ_NULLABLE RZ_OWN void *edge_data,
|
|
RZ_NULLABLE RzGraphEdgeChooser cb,
|
|
void *cb_data) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphNode *from = rz_graph_find_node(g, from_id);
|
|
RzGraphNode *to = rz_graph_find_node(g, to_id);
|
|
if (!from || !to) {
|
|
return RZ_GRAPH_STATUS_MISSING_NODE;
|
|
}
|
|
return rz_graph_update_edge(g, from, to, edge_data, cb, cb_data);
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a directed edge between two nodes.
|
|
*
|
|
* Dispatches to the impl backend to remove the edge and free its data.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If there was no edge to delete.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If an existing edge was delete.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_del_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphStatus s = g->impl_ops->del_edge(g, from, to);
|
|
if (s == RZ_GRAPH_STATUS_EXISTED) {
|
|
g->n_edges -= 1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* \brief Check if a directed edge exists between two nodes.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge exists.
|
|
* \return RZ_GRAPH_STATUS_MISSING_EDGE If edge doesn't exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_has_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, RZ_GRAPH_STATUS_ERR);
|
|
return g->impl_ops->has_edge(g, from, to);
|
|
}
|
|
|
|
/**
|
|
* \brief Find and return the edge between two nodes.
|
|
*
|
|
* \param g The graph.
|
|
* \param from source node
|
|
* \param to destination node
|
|
* \return the edge if found (borrowed), or NULL
|
|
*/
|
|
RZ_API RZ_BORROW RzGraphEdge *rz_graph_find_edge(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *from, RzGraphNode *to) {
|
|
rz_return_val_if_fail(g && from && to, NULL);
|
|
return g->impl_ops->find_edge(g, from, to);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all nodes in the graph.
|
|
*
|
|
* The iterator walks the node_vec in insertion order. Caller owns
|
|
* the returned iterator and must free it after use.
|
|
*
|
|
* \param g The graph.
|
|
* \return A new node iterator, or NULL on failure
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_get_nodes(const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzIterator *iter = pvector_as_iter(g->node_vec);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the number of bytes the graph covers in memory.
|
|
* NOTE: The real memory usage will always be a little bit larger.
|
|
*
|
|
* \param g The graph to get the memory usage for.
|
|
*
|
|
* \return The estimated size in bytes the graph covers in memory.
|
|
* Or 0 in case of failure, if the graph implementation doesn't tracking it.
|
|
*/
|
|
RZ_API ut64 rz_graph_mem_usage(const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, 0);
|
|
if (g->impl_ops->mem_usage) {
|
|
return g->impl_ops->mem_usage(g);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Return the number of nodes in the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \return node count
|
|
*/
|
|
RZ_API ut64 rz_graph_count_nodes(const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, 0);
|
|
return g->n_nodes;
|
|
}
|
|
|
|
/**
|
|
* \brief Return the number of edges in the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \return edge count
|
|
*/
|
|
RZ_API ut64 rz_graph_count_edges(const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, 0);
|
|
return g->n_edges;
|
|
}
|
|
|
|
typedef struct {
|
|
RzIterator *edge_iter;
|
|
bool use_from;
|
|
} RzNeighbourIterState;
|
|
|
|
/**
|
|
* \brief Iterator next callback that extracts neighbour nodes from edges.
|
|
*
|
|
* Depending on \p use_from, returns either edge->from (for in-neighbours)
|
|
* or edge->to (for out-neighbours).
|
|
*
|
|
* \param it the iterator
|
|
* \return next neighbour node, or NULL when exhausted
|
|
*/
|
|
static void *neighbour_iter_next(RzIterator *it) {
|
|
RzNeighbourIterState *state = (RzNeighbourIterState *)it->u;
|
|
RzGraphEdge *edge = rz_iterator_next(state->edge_iter);
|
|
if (!edge) {
|
|
return NULL;
|
|
}
|
|
|
|
if (state->use_from) {
|
|
return edge->from;
|
|
} else {
|
|
return edge->to;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Free callback for neighbour iterator state.
|
|
*
|
|
* Frees the inner edge iterator and the state struct.
|
|
*
|
|
* \param user_data the RzNeighbourIterState to free
|
|
*/
|
|
static void neighbour_iter_free(void *user_data) {
|
|
RzNeighbourIterState *state = (RzNeighbourIterState *)user_data;
|
|
if (!state) {
|
|
return;
|
|
}
|
|
rz_iterator_free(state->edge_iter);
|
|
state->edge_iter = NULL;
|
|
free(state);
|
|
}
|
|
|
|
/**
|
|
* \brief Wrap an edge iterator as a neighbour node iterator.
|
|
*
|
|
* Takes ownership of \p edge_iter and produces an iterator that yields
|
|
* neighbour nodes instead of edges.
|
|
*
|
|
* \param edge_iter the edge iterator to wrap (ownership transferred)
|
|
* \param use_from if true, yield edge->from; otherwise yield edge->to
|
|
* \return A new neighbour iterator, or NULL on failure
|
|
*/
|
|
static RZ_OWN RzIterator *as_neighbour_iter(RZ_OWN RzIterator *edge_iter, bool use_from) {
|
|
rz_return_val_if_fail(edge_iter, NULL);
|
|
RzNeighbourIterState *state = RZ_NEW0(RzNeighbourIterState);
|
|
if (!state) {
|
|
return NULL;
|
|
}
|
|
state->edge_iter = edge_iter;
|
|
state->use_from = use_from;
|
|
|
|
RzIterator *iter = rz_iterator_new(
|
|
neighbour_iter_next,
|
|
NULL,
|
|
neighbour_iter_free,
|
|
state);
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all outgoing neighbour nodes of \p n.
|
|
*
|
|
* \param g The graph.
|
|
* \param n the node
|
|
* \return A new neighbour iterator owned by caller, or NULL
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_out_neighbors(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *n) {
|
|
rz_return_val_if_fail(g && n, NULL);
|
|
RzIterator *edge_iter = g->impl_ops->get_out_edges(g, n);
|
|
if (!edge_iter) {
|
|
return NULL;
|
|
}
|
|
RzIterator *iter = as_neighbour_iter(edge_iter, false);
|
|
if (!iter) {
|
|
rz_iterator_free(edge_iter);
|
|
return NULL;
|
|
}
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all incoming neighbour nodes of \p n.
|
|
*
|
|
* \param g The graph.
|
|
* \param n the node
|
|
* \return A new neighbour iterator owned by caller, or NULL
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_in_neighbors(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *n) {
|
|
rz_return_val_if_fail(g && n, NULL);
|
|
RzIterator *edge_iter = g->impl_ops->get_in_edges(g, n);
|
|
if (!edge_iter) {
|
|
return NULL;
|
|
}
|
|
RzIterator *iter = as_neighbour_iter(edge_iter, true);
|
|
if (!iter) {
|
|
rz_iterator_free(edge_iter);
|
|
return NULL;
|
|
}
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the nth neighbour node of \p n.
|
|
*
|
|
* Iterates through out-edges or in-edges of \p n and returns the
|
|
* neighbour at position \p nth (0-indexed).
|
|
*
|
|
* \param g The graph.
|
|
* \param n the node
|
|
* \param nth 0-based index of the desired neighbour
|
|
* \param out_neighbor if true, get outgoing neighbours; otherwise incoming
|
|
* \return the nth neighbour node, or NULL if not enough neighbours
|
|
*/
|
|
RZ_API RzGraphNode *rz_graph_nth_neighbour(const RzGraph /*<NodeType *, EdgeType *>*/ *g, const RzGraphNode *n, ut64 nth, bool out_neighbor) {
|
|
rz_return_val_if_fail(g && n, NULL);
|
|
RzIterator *edge_iter = out_neighbor ? g->impl_ops->get_out_edges((RzGraph /*<NodeType *, EdgeType *>*/ *)g, (RzGraphNode *)n) : g->impl_ops->get_in_edges((RzGraph /*<NodeType *, EdgeType *>*/ *)g, (RzGraphNode *)n);
|
|
if (!edge_iter) {
|
|
return NULL;
|
|
}
|
|
|
|
RzGraphEdge *edge;
|
|
ut64 i = 0;
|
|
while ((edge = rz_iterator_next(edge_iter)) != NULL) {
|
|
if (i == nth) {
|
|
rz_iterator_free(edge_iter);
|
|
return out_neighbor ? edge->to : edge->from;
|
|
}
|
|
i += 1;
|
|
}
|
|
rz_iterator_free(edge_iter);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all outgoing edges of \p node.
|
|
*
|
|
* NOTE: edge iter is owned by caller, caller should free after use
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to get edges
|
|
* \return A new edge iterator, caller should free after use, or NULL if no edge or error
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_out_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, NULL);
|
|
return g->impl_ops->get_out_edges(g, node);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all incoming edges of \p node.
|
|
*
|
|
* NOTE: edge iter is owned by caller, caller should free after use
|
|
*
|
|
* \param g The graph.
|
|
* \param node node to get edges
|
|
* \return A new edge iterator, caller should free after use, or NULL if no edge or error
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_in_edges(RzGraph /*<NodeType *, EdgeType *>*/ *g, RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, NULL);
|
|
return g->impl_ops->get_in_edges(g, node);
|
|
}
|
|
|
|
RZ_API ut64 rz_graph_out_degree(const RzGraph /*<NodeType *, EdgeType *>*/ *g, const RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, 0);
|
|
ut32 count = 0;
|
|
RzIterator *it = g->impl_ops->get_out_edges((RzGraph /*<NodeType *, EdgeType *>*/ *)g, (RzGraphNode *)node);
|
|
if (it) {
|
|
RzGraphEdge *e;
|
|
rz_iterator_foreach(it, e) {
|
|
count += 1;
|
|
}
|
|
rz_iterator_free(it);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
RZ_API ut64 rz_graph_in_degree(const RzGraph /*<NodeType *, EdgeType *>*/ *g, const RzGraphNode *node) {
|
|
rz_return_val_if_fail(g && node, 0);
|
|
ut32 count = 0;
|
|
RzIterator *it = g->impl_ops->get_in_edges((RzGraph /*<NodeType *, EdgeType *>*/ *)g, (RzGraphNode *)node);
|
|
if (it) {
|
|
RzGraphEdge *e;
|
|
rz_iterator_foreach(it, e) {
|
|
count += 1;
|
|
}
|
|
rz_iterator_free(it);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the node's identifier.
|
|
*/
|
|
RZ_API ut64 rz_graph_node_get_id(RZ_NONNULL const RzGraphNode *node) {
|
|
rz_return_val_if_fail(node, 0);
|
|
return node->hash_id;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the index of the node in the internal vector.
|
|
*
|
|
* DO NOT USE!
|
|
*
|
|
* It only exists for compatibility reasons and because refactoring is
|
|
* more effort than currently acceptable.
|
|
*
|
|
* Justification:
|
|
* The old implementations had exactly two identifiers for nodes.
|
|
* The pointer to the node itself, or its index into the internal list storing it.
|
|
* I skip the part how what a not good design decision this was.
|
|
* But in the new graph this data is considered private (index into lists/hash table)
|
|
* or are not expected by the graph user to be tracked (the individual node pointers).
|
|
*
|
|
* Sadly the json output of the graph still uses the vector indices as "ids".
|
|
* We can't replace them with the actual hash_id of a node. Because most graphs don't implement a hash function.
|
|
* For these cases the pointer is used internally as node hash.
|
|
* And printing it in json output would not be stable.
|
|
* Hence this hack.
|
|
*/
|
|
RZ_DEPRECATE RZ_API ut64 rz_graph_node_get_vec_id(RZ_NONNULL const RzGraphNode *node) {
|
|
rz_return_val_if_fail(node, 0);
|
|
return node->_vec_id;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the internal node vector of the graph.
|
|
*
|
|
* DO NOT USE!
|
|
*
|
|
* It only exists for compatibility reasons and because refactoring is
|
|
* more effort than currently acceptable.
|
|
*/
|
|
RZ_DEPRECATE RZ_API const RzPVector /*<RzGraphNode *>*/ *rz_graph_get_node_vec(RZ_NONNULL const RzGraph /*<NodeType *, EdgeType *>*/ *g) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
return g->node_vec;
|
|
}
|
|
|
|
/**
|
|
* \brief Delete all edges for which \p cb returns true.
|
|
* If \p cb is NULL, it will delete all edges in the graph.
|
|
*
|
|
* NOTE: This function is slow! It has a runtime of O(|E| + |E|) or O(|N| * |N|)
|
|
*
|
|
* \param g The graph.
|
|
* \param cb The callback to decide which edge to delete. Can be NULL if all edges should be deleted.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_EXISTED If at least one edge was deleted.
|
|
* \return RZ_GRAPH_STATUS_OK If no edge was deleted.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_del_edges(RZ_BORROW RzGraph /*<NodeType *, EdgeTypde *>*/ *g, RZ_NULLABLE RzGraphEdgeChooser cb, void *cb_data) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
return g->impl_ops->del_edges(g, cb, cb_data);
|
|
}
|
|
|
|
/**
|
|
* \brief Delete a node in the graph by its identifier.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_EXISTED If node existed and was deleted.
|
|
* \return RZ_GRAPH_STATUS_OK If node did not exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_del_node_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphNode *node = rz_graph_find_node(g, hash_id);
|
|
if (!node) {
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
return rz_graph_del_node(g, node);
|
|
}
|
|
|
|
/**
|
|
* \brief Add an edge in the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \param from_id Node identifier
|
|
* \param to_id Node identifier
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge was added.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If edge existed.
|
|
* \return RZ_GRAPH_STATUS_MISSING_NODE If either node did not exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_add_edge_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 from_id, ut64 to_id, RZ_OWN void *edge_data) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphNode *from = rz_graph_find_node(g, from_id);
|
|
RzGraphNode *to = rz_graph_find_node(g, to_id);
|
|
if (!from || !to) {
|
|
return RZ_GRAPH_STATUS_MISSING_NODE;
|
|
}
|
|
return rz_graph_add_edge(g, from, to, edge_data);
|
|
}
|
|
|
|
/**
|
|
* \brief Delete an edge in the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \param from_id Node identifier.
|
|
* \param to_id Node identifier.
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If there was no edge to delete.
|
|
* \return RZ_GRAPH_STATUS_EXISTED If an existing edge was delete.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_del_edge_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 from_id, ut64 to_id) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphNode *from = rz_graph_find_node(g, from_id);
|
|
RzGraphNode *to = rz_graph_find_node(g, to_id);
|
|
if (!from || !to) {
|
|
return RZ_GRAPH_STATUS_OK;
|
|
}
|
|
return rz_graph_del_edge(g, from, to);
|
|
}
|
|
|
|
/**
|
|
* \brief Checks if the graph contains the edge (from_id, to_id).
|
|
*
|
|
* \param g The graph.
|
|
* \param from_id Node identifier
|
|
* \param to_id Node identifier
|
|
*
|
|
* \return RZ_GRAPH_STATUS_OK If edge exists.
|
|
* \return RZ_GRAPH_STATUS_MISSING_EDGE If edge doesn't exist.
|
|
* \return RZ_GRAPH_STATUS_ERR In case of error.
|
|
*/
|
|
RZ_API RzGraphStatus rz_graph_has_edge_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 from_id, ut64 to_id) {
|
|
rz_return_val_if_fail(g, RZ_GRAPH_STATUS_ERR);
|
|
RzGraphNode *from = rz_graph_find_node(g, from_id);
|
|
RzGraphNode *to = rz_graph_find_node(g, to_id);
|
|
if (!from || !to) {
|
|
return RZ_GRAPH_STATUS_MISSING_EDGE;
|
|
}
|
|
return rz_graph_has_edge(g, from, to);
|
|
}
|
|
|
|
/**
|
|
* \brief Finds an edge in the graph.
|
|
*
|
|
* \param g The graph.
|
|
* \param from_id Node identifier
|
|
* \param to_id Node identifier
|
|
* \return The edge data. Or NULL, if the edge has no data or in case of failure.
|
|
*/
|
|
RZ_API RZ_NULLABLE RZ_BORROW RzGraphEdge *rz_graph_find_edge_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 from_id, ut64 to_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *from = rz_graph_find_node(g, from_id);
|
|
RzGraphNode *to = rz_graph_find_node(g, to_id);
|
|
if (!from || !to) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_find_edge(g, from, to);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all outgoing edges of a node.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
* \return The iterator over <RZ_BORROW RzGraphEdge *> or NULL in case of failure.
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_out_edges_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *node = rz_graph_find_node(g, hash_id);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_out_edges(g, node);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all incoming edges of a node.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
* \return The iterator over <RZ_BORROW RzGraphEdge *> or NULL in case of failure.
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_in_edges_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *node = rz_graph_find_node(g, hash_id);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_in_edges(g, node);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all neighbors at outgoing edges of a node.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
* \return The iterator over <RZ_BORROW RzGraphNode *> or NULL in case of failure.
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_out_neighbors_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *node = rz_graph_find_node(g, hash_id);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_out_neighbors(g, node);
|
|
}
|
|
|
|
/**
|
|
* \brief Get an iterator over all neighbors at incoming edges of a node.
|
|
*
|
|
* \param g The graph.
|
|
* \param hash_id The node identifier.
|
|
* \return The iterator over <RZ_BORROW RzGraphNode *> or NULL in case of failure.
|
|
*/
|
|
RZ_API RZ_OWN RzIterator *rz_graph_in_neighbors_by_id(RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *node = rz_graph_find_node(g, hash_id);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_in_neighbors(g, node);
|
|
}
|
|
|
|
RZ_API ut64 rz_graph_out_degree_by_id(const RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, 0);
|
|
RzGraphNode *node = rz_graph_find_node((RzGraph /*<NodeType *, EdgeType *>*/ *)g, hash_id);
|
|
if (!node) {
|
|
return 0;
|
|
}
|
|
return rz_graph_out_degree(g, node);
|
|
}
|
|
|
|
RZ_API ut64 rz_graph_in_degree_by_id(const RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id) {
|
|
rz_return_val_if_fail(g, 0);
|
|
RzGraphNode *node = rz_graph_find_node((RzGraph /*<NodeType *, EdgeType *>*/ *)g, hash_id);
|
|
if (!node) {
|
|
return 0;
|
|
}
|
|
return rz_graph_in_degree(g, node);
|
|
}
|
|
|
|
RZ_API RZ_NULLABLE RZ_BORROW RzGraphNode *rz_graph_nth_neighbour_by_id(const RzGraph /*<NodeType *, EdgeType *>*/ *g, ut64 hash_id, ut64 nth, bool out_neighbor) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
RzGraphNode *node = rz_graph_find_node((RzGraph /*<NodeType *, EdgeType *>*/ *)g, hash_id);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
return rz_graph_nth_neighbour(g, node, nth, out_neighbor);
|
|
}
|
|
|
|
/**
|
|
* \brief Build a dotgraph representation of the graph.
|
|
*
|
|
* \param name An optional name of the graph.
|
|
* \param node_formatter An optional callback to get the node formatting.
|
|
* \param edge_formatter An optional callback to get the edge formatting.
|
|
*
|
|
* NOTE: The formatting string must be of the form: "[<dot graph formatting options>]"
|
|
*
|
|
* \return The dot graph string or NULL in case of failure.
|
|
*/
|
|
RZ_API RZ_OWN char *rz_graph_as_dot_str(
|
|
const RzGraph /*<NodeType *, EdgeType *>*/ *g,
|
|
RZ_NULLABLE const char *name,
|
|
RZ_NULLABLE RzGraphNodeFormatter node_formatter,
|
|
RZ_NULLABLE RzGraphEdgeFormatter edge_formatter) {
|
|
rz_return_val_if_fail(g, NULL);
|
|
|
|
RzStrBuf *sb = rz_strbuf_new("digraph ");
|
|
if (!sb) {
|
|
return NULL;
|
|
}
|
|
if (name) {
|
|
rz_strbuf_appendf(sb, "\"%s\" \{\n", name);
|
|
} else {
|
|
rz_strbuf_appendf(sb, "\{\n");
|
|
}
|
|
|
|
#define INDENT " "
|
|
|
|
RzIterator *nodes = rz_graph_get_nodes(g);
|
|
RzGraphNode *n;
|
|
rz_iterator_foreach(nodes, n) {
|
|
char node_name[64] = { 0 };
|
|
rz_strf(node_name, "%" PFMT64d, rz_graph_node_get_id(n));
|
|
|
|
char *node_format = NULL;
|
|
if (node_formatter && (node_format = node_formatter(n))) {
|
|
rz_strbuf_appendf(sb, INDENT "%s %s\n", node_name, node_format);
|
|
free(node_format);
|
|
}
|
|
|
|
RzIterator *out_edges = rz_graph_out_edges((RzGraph *)g, n);
|
|
if (!out_edges) {
|
|
continue;
|
|
}
|
|
RzGraphEdge *e;
|
|
rz_iterator_foreach(out_edges, e) {
|
|
rz_strbuf_appendf(sb, INDENT "%s -> %" PFMT64d,
|
|
node_name,
|
|
rz_graph_node_get_id(rz_graph_edge_get_to(e)));
|
|
|
|
char *edge_format = NULL;
|
|
if (edge_formatter && (edge_format = edge_formatter(e))) {
|
|
rz_strbuf_appendf(sb, " %s\n", edge_format);
|
|
free(edge_format);
|
|
} else {
|
|
rz_strbuf_append(sb, "\n");
|
|
}
|
|
}
|
|
rz_iterator_free(out_edges);
|
|
}
|
|
rz_iterator_free(nodes);
|
|
rz_strbuf_append(sb, "}\n");
|
|
return rz_strbuf_drain(sb);
|
|
}
|
|
|
|
#undef LIST_IMPL_DEFAULT_EDGE_VEC_SIZE
|