Skip to content

Commit

Permalink
Add acg - printing class hierarchy graph ##anal (#17362)
Browse files Browse the repository at this point in the history
* add `acg` - printing class hierarchy graph
* Move the printing into the cmd_anal.c from returned RAGraph
* Change API to return Graph and transform it to AGraph when printing is necessary
* Move graph to agraph into agraph module, move generic node structure to rgraph, removing unnecessary dependency
* Move RGraphNodeInfo related functions to graph.c
  • Loading branch information
HoundThe committed Aug 7, 2020
1 parent e67aada commit b44b8cb
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 0 deletions.
80 changes: 80 additions & 0 deletions libr/anal/class.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <r_anal.h>
#include <r_vector.h>
#include "../include/r_anal.h"
#include "../include/r_util/r_graph.h"

static void r_anal_class_base_delete_class(RAnal *anal, const char *class_name);
static void r_anal_class_method_delete_class(RAnal *anal, const char *class_name);
Expand Down Expand Up @@ -1218,3 +1219,82 @@ R_API void r_anal_class_list_vtable_offset_functions(RAnal *anal, const char *cl
ls_free (classes);
}
}

/**
* @brief Creates RGraph from class inheritance information where
* each node has RGraphNodeInfo as generic data
*
* @param anal
* @return RGraph* NULL if failure
*/
R_API RGraph *r_anal_class_get_inheritance_graph(RAnal *anal) {
r_return_val_if_fail (anal, NULL);
RGraph *class_graph = r_graph_new ();
if (!class_graph) {
return NULL;
}
SdbList *classes = r_anal_class_get_all (anal, true);
if (!classes) {
r_graph_free (class_graph);
return NULL;
}
// O(1) lookup cache for processed nodes
// to lookup parents to create edges
HtPP /*<char *name, RGraphNode *node>*/ *hashmap = ht_pp_new0 ();
if (!hashmap) {
r_graph_free (class_graph);
ls_free (classes);
}
SdbListIter *iter;
SdbKv *kv;
// Traverse each class and create a node and edges
ls_foreach (classes, iter, kv) {
const char *name = sdbkv_key (kv);
// If already in the cache
RGraphNode *curr_node = ht_pp_find (hashmap, name, NULL);
if (!curr_node) { // If not visited yet
RGraphNodeInfo *data = r_graph_create_node_info (strdup (name), NULL, 0);
if (!data) {
goto failure;
}
curr_node = r_graph_add_node (class_graph, data);
if (!curr_node) {
goto failure;
}
curr_node->free = r_graph_free_node_info;
ht_pp_insert (hashmap, name, curr_node);
}
// Travel all bases to create edges between parents and child
RVector *bases = r_anal_class_base_get_all (anal, name);
RAnalBaseClass *base;
r_vector_foreach (bases, base) {
bool base_found = false;
RGraphNode *base_node = ht_pp_find (hashmap, base->class_name, &base_found);
// If base isn't processed, do it now
if (!base_found) {
// Speed it up and already add base classes if not visited yet
RGraphNodeInfo *data = r_graph_create_node_info (strdup (base->class_name), NULL, 0);
if (!data) {
goto failure;
}
base_node = r_graph_add_node (class_graph, data);
if (!base_node) {
goto failure;
}
base_node->free = r_graph_free_node_info;
ht_pp_insert (hashmap, base->class_name, base_node);
}
r_graph_add_edge (class_graph, base_node, curr_node);
}
r_vector_free (bases);
}
ls_free (classes);
ht_pp_free (hashmap);
return class_graph;

failure:
ls_free (classes);
ht_pp_free (hashmap);
r_graph_free (class_graph);
return NULL;
}
58 changes: 58 additions & 0 deletions libr/core/agraph.c
Original file line number Diff line number Diff line change
Expand Up @@ -4925,3 +4925,61 @@ R_API int r_core_visual_graph(RCore *core, RAGraph *g, RAnalFunction *_fcn, int
}
return !is_error;
}

/**
* @brief Create RAGraph from generic RGraph with RGraphNodeInfo as node data
*
* @param graph <RGraphNodeInfo>
* @return RAGraph* NULL if failure
*/
R_API RAGraph *create_agraph_from_graph(const RGraph/*<RGraphNodeInfo>*/ *graph) {
r_return_val_if_fail (graph, NULL);

RAGraph *result_agraph = r_agraph_new (r_cons_canvas_new (1, 1));
if (!result_agraph) {
return NULL;
}
// Cache lookup to build edges
HtPP /*<RGraphNode *node, RANode *anode>*/ *hashmap = ht_pp_new0 ();
if (!hashmap) {
r_agraph_free (result_agraph);
return NULL;
}
// List of the new RANodes
RListIter *iter;
RGraphNode *node;
// Traverse the list, create new ANode for each Node
r_list_foreach (graph->nodes, iter, node) {
RGraphNodeInfo *info = node->data;
RANode *a_node = r_agraph_add_node (result_agraph, info->title, info->body);
if (!a_node) {
goto failure;
}
ht_pp_insert (hashmap, node, a_node);
}

// Traverse the nodes again, now build up the edges
r_list_foreach (graph->nodes, iter, node) {
RANode *a_node = ht_pp_find (hashmap, node, NULL);
if (!a_node) {
goto failure; // shouldn't happen in correct graph state
}

RListIter *neighbour_iter;
RGraphNode *neighbour;
r_list_foreach (node->in_nodes, neighbour_iter, neighbour) {
RANode *a_neighbour = ht_pp_find (hashmap, neighbour, NULL);
if (!a_neighbour) {
goto failure;
}
r_agraph_add_edge (result_agraph, a_neighbour, a_node);
}
}

ht_pp_free (hashmap);
return result_agraph;
failure:
ht_pp_free (hashmap);
r_agraph_free (result_agraph);
return NULL;
}
15 changes: 15 additions & 0 deletions libr/core/cmd_anal.c
Original file line number Diff line number Diff line change
Expand Up @@ -9910,6 +9910,21 @@ static void cmd_anal_classes(RCore *core, const char *input) {
case 'm': // "acm"
cmd_anal_class_method (core, input + 1);
break;
case 'g': { // "acg"
RGraph *graph = r_anal_class_get_inheritance_graph (core->anal);
if (!graph) {
eprintf ("Couldn't create graph");
break;
}
RAGraph *agraph = create_agraph_from_graph (graph);
if (!agraph) {
eprintf ("Couldn't create graph");
break;
}
r_agraph_print (agraph);
r_graph_free (graph);
r_agraph_free (agraph);
} break;
default: // "ac?"
r_core_cmd_help (core, help_msg_ac);
break;
Expand Down
1 change: 1 addition & 0 deletions libr/include/r_agraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ R_API Sdb *r_agraph_get_sdb(RAGraph *g);
R_API void r_agraph_foreach(RAGraph *g, RANodeCallback cb, void *user);
R_API void r_agraph_foreach_edge(RAGraph *g, RAEdgeCallback cb, void *user);
R_API void r_agraph_set_curnode(RAGraph *g, RANode *node);
R_API RAGraph *create_agraph_from_graph(const RGraph/*<RGraphNodeInfo>*/ *graph);
#endif

#endif
1 change: 1 addition & 0 deletions libr/include/r_anal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,7 @@ R_API void r_anal_class_list(RAnal *anal, int mode);
R_API void r_anal_class_list_bases(RAnal *anal, const char *class_name);
R_API void r_anal_class_list_vtables(RAnal *anal, const char *class_name);
R_API void r_anal_class_list_vtable_offset_functions(RAnal *anal, const char *class_name, ut64 offset);
R_API RGraph/*<RGraphNodeInfo>*/ *r_anal_class_get_inheritance_graph(RAnal *anal);

R_API RAnalEsilCFG *r_anal_esil_cfg_expr(RAnalEsilCFG *cfg, RAnal *anal, const ut64 off, char *expr);
R_API RAnalEsilCFG *r_anal_esil_cfg_op(RAnalEsilCFG *cfg, RAnal *anal, RAnalOp *op);
Expand Down
11 changes: 11 additions & 0 deletions libr/include/r_util/r_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
extern "C" {
#endif

/**
* @brief Generic graph node info
*/
typedef struct r_anal_graph_node_info_t {
char *title;
char *body;
ut64 offset;
} RGraphNodeInfo;

typedef struct r_graph_node_t {
unsigned int idx;
void *data;
Expand Down Expand Up @@ -64,6 +73,8 @@ R_API bool r_graph_adjacent(const RGraph *g, const RGraphNode *from, const RGrap
R_API void r_graph_dfs_node(RGraph *g, RGraphNode *n, RGraphVisitor *vis);
R_API void r_graph_dfs_node_reverse(RGraph *g, RGraphNode *n, RGraphVisitor *vis);
R_API void r_graph_dfs(RGraph *g, RGraphVisitor *vis);
R_API void r_graph_free_node_info(void *ptr);
R_API RGraphNodeInfo *r_graph_create_node_info(char *title, char *body, ut64 offset);

#ifdef __cplusplus
}
Expand Down
17 changes: 17 additions & 0 deletions libr/util/graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,20 @@ R_API void r_graph_dfs(RGraph *g, RGraphVisitor *vis) {
free (color);
}
}

R_API void r_graph_free_node_info(void *ptr) {
RGraphNodeInfo *info = ptr;
free (info->body);
free (info->title);
free (info);
}

R_API RGraphNodeInfo *r_graph_create_node_info(char *title, char *body, ut64 offset) {
RGraphNodeInfo *data = R_NEW0 (RGraphNodeInfo);
if (data) {
data->title = title;
data->body = body;
data->offset = offset;
}
return data;
}
1 change: 1 addition & 0 deletions test/unit/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ if get_option('enable_tests')
'anal_meta',
'anal_var',
'anal_xrefs',
'anal_class_graph',
'annotated_code',
'base64',
'bin',
Expand Down
109 changes: 109 additions & 0 deletions test/unit/test_agraph.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include <r_core.h>
#include <r_anal.h>
#include <r_agraph.h>
#include <r_util.h>
#include "minunit.h"

bool test_graph_to_agraph() {
RCore *core = r_core_new ();
r_core_cmd0 (core, "ac A");
r_core_cmd0 (core, "ac B");
r_core_cmd0 (core, "ac C");
r_core_cmd0 (core, "ac D");
r_core_cmd0 (core, "acb B A");
r_core_cmd0 (core, "acb C A");
r_core_cmd0 (core, "acb D B");
r_core_cmd0 (core, "acb D C");

RGraph *graph = r_anal_class_get_inheritance_graph (core->anal);
mu_assert_notnull (graph, "Couldn't create the graph");
mu_assert_eq (graph->nodes->length, 4, "Wrong node count");

RAGraph *agraph = create_agraph_from_graph (graph);
mu_assert_notnull (agraph, "Couldn't create the graph");
mu_assert_eq (agraph->graph->nodes->length, 4, "Wrong node count");

RListIter *iter;
RGraphNode *node;
int i = 0;
ls_foreach (agraph->graph->nodes, iter, node) {
RANode *info = node->data;
switch (i++) {
case 0:
mu_assert_streq (info->title, "A", "Wrong node name");
mu_assert_eq (node->out_nodes->length, 2, "Wrong node out-nodes");
{
RListIter *iter;
RGraphNode *out_node;
int i = 0;
ls_foreach (node->out_nodes, iter, out_node) {
RANode *info = out_node->data;
switch (i++) {
case 0:
mu_assert_streq (info->title, "B", "Wrong node name");
break;
case 1:
mu_assert_streq (info->title, "C", "Wrong node name");
break;
}
}
}
break;
case 1:
mu_assert_streq (info->title, "B", "Wrong node name");
mu_assert_eq (node->out_nodes->length, 1, "Wrong node out-nodes");
mu_assert_eq (node->in_nodes->length, 1, "Wrong node in-nodes");
{
RListIter *iter;
RGraphNode *out_node;
int i = 0;
ls_foreach (node->out_nodes, iter, out_node) {
RANode *info = out_node->data;
switch (i++) {
case 0:
mu_assert_streq (info->title, "D", "Wrong node name");
break;
}
}
}
break;
case 2:
mu_assert_streq (info->title, "C", "Wrong node name");
mu_assert_eq (node->out_nodes->length, 1, "Wrong node out-nodes");
mu_assert_eq (node->in_nodes->length, 1, "Wrong node in-nodes");
{
RListIter *iter;
RGraphNode *out_node;
int i = 0;
ls_foreach (node->out_nodes, iter, out_node) {
RANode *info = out_node->data;
switch (i++) {
case 0:
mu_assert_streq (info->title, "D", "Wrong node name");
break;
}
}
}
break;
case 3:
mu_assert_streq (info->title, "D", "Wrong node name");
mu_assert_eq (node->in_nodes->length, 2, "Wrong node in-nodes");
break;
default:
break;
}
}
r_core_free (core);
r_graph_free (graph);
r_agraph_free (agraph);
mu_end;
}

int all_tests() {
mu_run_test (test_graph_to_agraph);
return tests_passed != tests_run;
}

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

0 comments on commit b44b8cb

Please sign in to comment.