2248 lines
56 KiB
C
2248 lines
56 KiB
C
/* Copyright radare2 2014-2015 - Author: pancake */
|
|
|
|
#include <r_core.h>
|
|
#include <limits.h>
|
|
|
|
static const char *mousemodes[] = { "canvas-y", "canvas-x", "node-y", "node-x", NULL };
|
|
static int mousemode = 0;
|
|
|
|
#define BORDER 3
|
|
#define BORDER_WIDTH 4
|
|
#define BORDER_HEIGHT 3
|
|
#define MARGIN_TEXT_X 2
|
|
#define MARGIN_TEXT_Y 2
|
|
#define HORIZONTAL_NODE_SPACING 12
|
|
#define VERTICAL_NODE_SPACING 4
|
|
#define MIN_NODE_WIDTH 18
|
|
#define MIN_NODE_HEIGTH BORDER_HEIGHT
|
|
#define INIT_HISTORY_CAPACITY 16
|
|
#define TITLE_LEN 128
|
|
#define DEFAULT_SPEED 1
|
|
#define SMALLNODE_TEXT "[____]"
|
|
#define SMALLNODE_TEXT_CUR "[_@@_]"
|
|
|
|
#define ZOOM_STEP 10
|
|
#define ZOOM_DEFAULT 100
|
|
|
|
#define history_push(stack, x) (r_stack_push (stack, (void *)(size_t)x))
|
|
#define history_pop(stack) ((RGraphNode *)r_stack_pop (stack))
|
|
|
|
#define hash_set(sdb,k,v) (sdb_num_set (sdb, sdb_fmt (0, "%"PFMT64u, (ut64)(size_t)k), (ut64)(size_t)v, 0))
|
|
#define hash_get(sdb,k) (sdb_num_get (sdb, sdb_fmt (0, "%"PFMT64u, (ut64)(size_t)k), NULL))
|
|
#define hash_get_rnode(sdb,k) ((RGraphNode *)(size_t)hash_get (sdb, k))
|
|
#define hash_get_rlist(sdb,k) ((RList *)(size_t)hash_get (sdb, k))
|
|
#define hash_get_int(sdb,k) ((int)hash_get (sdb, k))
|
|
|
|
#define get_anode(gn) ((ANode *)gn->data)
|
|
|
|
#define graph_foreach_anode(list, it, pos, anode) \
|
|
if (list) for (it = list->head; it && (pos = it->data) && (pos) && (anode = (ANode *)pos->data); it = it->n)
|
|
|
|
struct len_pos_t {
|
|
int len;
|
|
int pos;
|
|
};
|
|
|
|
struct dist_t {
|
|
const RGraphNode *from;
|
|
const RGraphNode *to;
|
|
int dist;
|
|
};
|
|
|
|
struct layer_t {
|
|
int n_nodes;
|
|
RGraphNode **nodes;
|
|
int position;
|
|
int height;
|
|
};
|
|
|
|
typedef struct ascii_node {
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
ut64 addr;
|
|
int layer;
|
|
int pos_in_layer;
|
|
char *text;
|
|
int is_dummy;
|
|
int is_reversed;
|
|
int class;
|
|
} ANode;
|
|
|
|
typedef struct ascii_graph {
|
|
RCore *core;
|
|
RConsCanvas *can;
|
|
RAnalFunction *fcn;
|
|
RGraph *graph;
|
|
const RGraphNode *curnode;
|
|
|
|
int is_callgraph;
|
|
int is_instep;
|
|
int is_simple_mode;
|
|
int is_small_nodes;
|
|
int zoom;
|
|
int movspeed;
|
|
|
|
RStack *history;
|
|
ANode *update_seek_on;
|
|
int need_reload_nodes;
|
|
int need_set_layout;
|
|
int need_update_dim;
|
|
int force_update_seek;
|
|
|
|
/* layout algorithm info */
|
|
RList *back_edges;
|
|
RList *long_edges;
|
|
struct layer_t *layers;
|
|
int n_layers;
|
|
RList *dists; /* RList<struct dist_t> */
|
|
} AGraph;
|
|
|
|
struct agraph_refresh_data {
|
|
AGraph *g;
|
|
int fs;
|
|
};
|
|
|
|
#define G(x,y) r_cons_canvas_gotoxy (g->can, x, y)
|
|
#define W(x) r_cons_canvas_write (g->can, x)
|
|
#define B(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,NULL)
|
|
#define B1(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,Color_BLUE)
|
|
#define B2(x,y,w,h) r_cons_canvas_box(g->can, x,y,w,h,Color_MAGENTA)
|
|
#define L(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,0)
|
|
#define L1(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,1)
|
|
#define L2(x,y,x2,y2) r_cons_canvas_line(g->can, x,y,x2,y2,2)
|
|
#define F(x,y,x2,y2,c) r_cons_canvas_fill(g->can, x,y,x2,y2,c,0)
|
|
|
|
static ANode *ascii_node_new (int is_dummy) {
|
|
ANode *res = R_NEW0 (ANode);
|
|
if (!res) return NULL;
|
|
res->layer = -1;
|
|
res->pos_in_layer = -1;
|
|
res->is_dummy = is_dummy;
|
|
res->is_reversed = R_FALSE;
|
|
res->class = -1;
|
|
return res;
|
|
}
|
|
|
|
static void update_node_dimension(const RGraph *g, int is_small, int zoom) {
|
|
const RList *nodes = r_graph_get_nodes (g);
|
|
RGraphNode *gn;
|
|
RListIter *it;
|
|
ANode *n;
|
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
if (is_small) {
|
|
n->h = 0;
|
|
n->w = strlen (SMALLNODE_TEXT);
|
|
} else {
|
|
n->w = r_str_bounds (n->text, &n->h);
|
|
n->w += BORDER_WIDTH;
|
|
n->h += BORDER_HEIGHT;
|
|
/* scale node by zoom */
|
|
n->w = R_MAX (MIN_NODE_WIDTH, (n->w * zoom) / 100);
|
|
n->h = R_MAX (MIN_NODE_HEIGTH, (n->h * zoom) / 100);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void small_ANode_print(const AGraph *g, const ANode *n, int cur) {
|
|
char title[TITLE_LEN];
|
|
|
|
if (!G (n->x + 2, n->y - 1))
|
|
return;
|
|
if (cur) {
|
|
W(SMALLNODE_TEXT_CUR);
|
|
(void)G (-g->can->sx, -g->can->sy + 2);
|
|
snprintf (title, sizeof (title) - 1,
|
|
"0x%08"PFMT64x":", n->addr);
|
|
W (title);
|
|
(void)G (-g->can->sx, -g->can->sy + 3);
|
|
W (n->text);
|
|
} else {
|
|
W(SMALLNODE_TEXT);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void normal_ANode_print(const AGraph *g, const ANode *n, int cur) {
|
|
unsigned int center_x = 0, center_y = 0;
|
|
unsigned int delta_x = 0, delta_txt_x = 0;
|
|
unsigned int delta_y = 0, delta_txt_y = 0;
|
|
char title[TITLE_LEN];
|
|
char *text;
|
|
int x, y;
|
|
|
|
#if SHOW_OUT_OF_SCREEN_NODES
|
|
x = n->x + g->can->sx;
|
|
y = n->y + n->h + g->can->sy;
|
|
if (x < 0 || x > g->can->w)
|
|
return;
|
|
if (y < 0 || y > g->can->h)
|
|
return;
|
|
#endif
|
|
x = n->x + g->can->sx;
|
|
y = n->y + g->can->sy;
|
|
if (x + MARGIN_TEXT_X < 0)
|
|
delta_x = -(x + MARGIN_TEXT_X);
|
|
if (x + n->w < -MARGIN_TEXT_X)
|
|
return;
|
|
if (y < -1)
|
|
delta_y = R_MIN (n->h - BORDER_HEIGHT - 1, -y - MARGIN_TEXT_Y);
|
|
|
|
/* print the title */
|
|
if (cur) {
|
|
snprintf (title, sizeof (title)-1,
|
|
"[0x%08"PFMT64x"]", n->addr);
|
|
} else {
|
|
snprintf (title, sizeof (title)-1,
|
|
" 0x%08"PFMT64x" ", n->addr);
|
|
}
|
|
if (delta_x < strlen(title) && G(n->x + MARGIN_TEXT_X + delta_x, n->y + 1))
|
|
W(title + delta_x);
|
|
|
|
/* print the body */
|
|
if (g->zoom > ZOOM_DEFAULT) {
|
|
center_x = (g->zoom - ZOOM_DEFAULT) / 20;
|
|
center_y = (g->zoom - ZOOM_DEFAULT) / 30;
|
|
delta_txt_x = R_MIN (delta_x, center_x);
|
|
delta_txt_y = R_MIN (delta_y, center_y);
|
|
}
|
|
|
|
if (G(n->x + MARGIN_TEXT_X + delta_x + center_x - delta_txt_x,
|
|
n->y + MARGIN_TEXT_Y + delta_y + center_y - delta_txt_y)) {
|
|
unsigned int text_x = center_x >= delta_x ? 0 : delta_x - center_x;
|
|
unsigned int text_y = center_y >= delta_y ? 0 : delta_y - center_y;
|
|
unsigned int text_h = BORDER_HEIGHT >= n->h ? 0 : n->h - BORDER_HEIGHT;
|
|
|
|
if (g->zoom < ZOOM_DEFAULT) text_h--;
|
|
if (text_y <= text_h - 1) {
|
|
text = r_str_crop (n->text,
|
|
text_x, text_y,
|
|
n->w - BORDER_WIDTH,
|
|
text_h);
|
|
if (text) {
|
|
W (text);
|
|
if (g->zoom < ZOOM_DEFAULT) W ("\n");
|
|
free (text);
|
|
} else {
|
|
W (n->text);
|
|
}
|
|
}
|
|
/* print some dots when the text is cropped because of zoom */
|
|
if (text_y <= text_h && g->zoom < ZOOM_DEFAULT) {
|
|
char *dots = "...";
|
|
if (delta_x < strlen(dots)) {
|
|
dots += delta_x;
|
|
W (dots);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: check if node is traced or not and hsow proper color
|
|
// This info must be stored inside ANode* from RCore*
|
|
if (cur) {
|
|
B1 (n->x, n->y, n->w, n->h);
|
|
} else {
|
|
B (n->x, n->y, n->w, n->h);
|
|
}
|
|
}
|
|
|
|
static int **get_crossing_matrix (const RGraph *g,
|
|
const struct layer_t layers[],
|
|
int maxlayer, int i, int from_up,
|
|
int *n_rows) {
|
|
int len = layers[i].n_nodes;
|
|
int **m;
|
|
int j;
|
|
|
|
m = R_NEWS0 (int *, len);
|
|
if (!m)
|
|
return NULL;
|
|
|
|
for (j = 0; j < len; ++j) {
|
|
m[j] = R_NEWS0 (int, len);
|
|
if (!m[j])
|
|
goto err_row;
|
|
}
|
|
|
|
/* calculate crossings between layer i and layer i-1 */
|
|
/* consider the crossings generated by each pair of edges */
|
|
if (i > 0 && from_up) {
|
|
for (j = 0; j < layers[i - 1].n_nodes; ++j) {
|
|
const RGraphNode *gj = layers[i - 1].nodes[j];
|
|
const RList *neigh = r_graph_get_neighbours (g, gj);
|
|
RGraphNode *gk;
|
|
RListIter *itk;
|
|
|
|
r_list_foreach (neigh, itk, gk) {
|
|
int s;
|
|
for (s = 0; s < j; ++s) {
|
|
const RGraphNode *gs = layers[i - 1].nodes[s];
|
|
const RList *neigh_s = r_graph_get_neighbours (g, gs);
|
|
RGraphNode *gt;
|
|
RListIter *itt;
|
|
|
|
r_list_foreach (neigh_s, itt, gt) {
|
|
const ANode *ak, *at; /* k and t should be "indexes" on layer i */
|
|
|
|
if (gt == gk) continue;
|
|
ak = (ANode *)gk->data;
|
|
at = (ANode *)gt->data;
|
|
if (ak->layer != i || at->layer != i) {
|
|
eprintf("%llx or %llx are not on the right layer (%d)\n", ak->addr, at->addr, i);
|
|
eprintf("edge from %llx to %llx is wrong\n", ((ANode*)(gj->data))->addr, ak->addr);
|
|
eprintf("edge from %llx to %llx is wrong\n\n", ((ANode*)(gs->data))->addr, at->addr);
|
|
|
|
continue;
|
|
}
|
|
m[ak->pos_in_layer][at->pos_in_layer]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* calculate crossings between layer i and layer i+1 */
|
|
if (i < maxlayer - 1 && !from_up) {
|
|
for (j = 0; j < layers[i].n_nodes; ++j) {
|
|
const RGraphNode *gj = layers[i].nodes[j];
|
|
const RList *neigh = r_graph_get_neighbours (g, gj);
|
|
const ANode *ak, *aj = (ANode *)gj->data;
|
|
RGraphNode *gk;
|
|
RListIter *itk;
|
|
|
|
graph_foreach_anode (neigh, itk, gk, ak) {
|
|
int s;
|
|
for (s = 0; s < layers[i].n_nodes; ++s) {
|
|
const RGraphNode *gs = layers[i].nodes[s];
|
|
const RList *neigh_s;
|
|
RGraphNode *gt;
|
|
RListIter *itt;
|
|
const ANode *at, *as = (ANode *)gs->data;
|
|
|
|
if (gs == gj) continue;
|
|
neigh_s = r_graph_get_neighbours (g, gs);
|
|
graph_foreach_anode (neigh_s, itt, gt, at) {
|
|
if (at->pos_in_layer < ak->pos_in_layer)
|
|
m[aj->pos_in_layer][as->pos_in_layer]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n_rows)
|
|
*n_rows = len;
|
|
return m;
|
|
|
|
err_row:
|
|
for (i = 0; i < len; ++i) {
|
|
if (m[i])
|
|
free (m[i]);
|
|
}
|
|
free (m);
|
|
return NULL;
|
|
}
|
|
|
|
static int layer_sweep (const RGraph *g, const struct layer_t layers[],
|
|
int maxlayer, int i, int from_up) {
|
|
int **cross_matrix;
|
|
RGraphNode *u, *v;
|
|
const ANode *au, *av;
|
|
int n_rows, j, changed = R_FALSE;
|
|
int len = layers[i].n_nodes;
|
|
|
|
cross_matrix = get_crossing_matrix (g, layers, maxlayer, i, from_up, &n_rows);
|
|
if (!cross_matrix) return R_FALSE;
|
|
|
|
for (j = 0; j < len - 1; ++j) {
|
|
int auidx, avidx;
|
|
|
|
u = layers[i].nodes[j];
|
|
v = layers[i].nodes[j + 1];
|
|
au = (ANode *)u->data;
|
|
av = (ANode *)v->data;
|
|
auidx = au->pos_in_layer;
|
|
avidx = av->pos_in_layer;
|
|
|
|
if (cross_matrix[auidx][avidx] > cross_matrix[avidx][auidx]) {
|
|
/* swap elements */
|
|
layers[i].nodes[j] = v;
|
|
layers[i].nodes[j + 1] = u;
|
|
changed = R_TRUE;
|
|
}
|
|
}
|
|
|
|
/* update position in the layer of each node. During the swap of some
|
|
* elements we didn't swap also the pos_in_layer because the cross_matrix
|
|
* is indexed by it, so do it now! */
|
|
for (j = 0; j < layers[i].n_nodes; ++j) {
|
|
ANode *n = (ANode *)layers[i].nodes[j]->data;
|
|
n->pos_in_layer = j;
|
|
}
|
|
|
|
for (j = 0; j < n_rows; ++j)
|
|
free (cross_matrix[j]);
|
|
free (cross_matrix);
|
|
return changed;
|
|
}
|
|
|
|
static void view_cyclic_edge (RGraphNode *from, RGraphNode *to, const RGraphVisitor *vis) {
|
|
const AGraph *g = (AGraph *)vis->data;
|
|
RGraphEdge *e = R_NEW (RGraphEdge);
|
|
|
|
e->from = from;
|
|
e->to = to;
|
|
r_list_append (g->back_edges, e);
|
|
}
|
|
|
|
static int get_depth (Sdb *path, const RGraphNode *n) {
|
|
int res = 0;
|
|
while ((n = hash_get_rnode (path, n)) != NULL) {
|
|
res++;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void set_layer (const RGraphNode *from, const RGraphNode *to, const RGraphVisitor *vis) {
|
|
Sdb *path = (Sdb *)vis->data;
|
|
int bdepth, adepth;
|
|
|
|
adepth = get_depth (path, from);
|
|
bdepth = get_depth (path, to);
|
|
|
|
if (adepth + 1 > bdepth)
|
|
hash_set (path, to, from);
|
|
}
|
|
|
|
static void view_dummy (RGraphNode *from, RGraphNode *to, const RGraphVisitor *vis) {
|
|
const ANode *a = (ANode *)from->data;
|
|
const ANode *b = (ANode *)to->data;
|
|
RList *long_edges = (RList *)vis->data;
|
|
|
|
if (R_ABS (a->layer - b->layer) > 1) {
|
|
RGraphEdge *e = R_NEW (RGraphEdge);
|
|
e->from = from;
|
|
e->to = to;
|
|
r_list_append (long_edges, e);
|
|
}
|
|
}
|
|
|
|
/* find a set of edges that, removed, makes the graph acyclic */
|
|
/* invert the edges identified in the previous step */
|
|
static void remove_cycles (AGraph *g) {
|
|
RGraphVisitor cyclic_vis = { NULL, NULL, NULL, NULL, NULL, NULL };
|
|
const RGraphEdge *e;
|
|
const RListIter *it;
|
|
|
|
g->back_edges = r_list_new();
|
|
cyclic_vis.back_edge = (RGraphEdgeCallback)view_cyclic_edge;
|
|
cyclic_vis.data = g;
|
|
r_graph_dfs (g->graph, &cyclic_vis);
|
|
|
|
r_list_foreach (g->back_edges, it, e) {
|
|
r_graph_del_edge (g->graph, e->from, e->to);
|
|
r_graph_add_edge (g->graph, e->to, e->from);
|
|
}
|
|
}
|
|
|
|
/* assign a layer to each node of the graph */
|
|
static void assign_layers (const AGraph *g) {
|
|
RGraphVisitor layer_vis = { NULL, NULL, NULL, NULL, NULL, NULL };
|
|
Sdb *path_layers = sdb_new0 ();
|
|
const RGraphNode *gn;
|
|
const RListIter *it;
|
|
ANode *n;
|
|
|
|
layer_vis.data = path_layers;
|
|
layer_vis.tree_edge = (RGraphEdgeCallback)set_layer;
|
|
layer_vis.fcross_edge = (RGraphEdgeCallback)set_layer;
|
|
r_graph_dfs (g->graph, &layer_vis);
|
|
|
|
graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) {
|
|
n->layer = get_depth (path_layers, gn);
|
|
}
|
|
|
|
sdb_free (path_layers);
|
|
}
|
|
|
|
static int find_edge (const RGraphEdge *a, const RGraphEdge *b) {
|
|
return a->from == b->to && a->to == b->from ? 0 : 1;
|
|
}
|
|
|
|
static int is_reversed (const AGraph *g, const RGraphEdge *e) {
|
|
return r_list_find (g->back_edges, e, (RListComparator)find_edge) ? R_TRUE : R_FALSE;
|
|
}
|
|
|
|
/* add dummy nodes when there are edges that span multiple layers */
|
|
static void create_dummy_nodes (AGraph *g) {
|
|
RGraphVisitor dummy_vis = { NULL, NULL, NULL, NULL, NULL, NULL };
|
|
const RListIter *it;
|
|
const RGraphEdge *e;
|
|
|
|
g->long_edges = r_list_new ();
|
|
dummy_vis.data = g->long_edges;
|
|
dummy_vis.tree_edge = (RGraphEdgeCallback)view_dummy;
|
|
dummy_vis.fcross_edge = (RGraphEdgeCallback)view_dummy;
|
|
r_graph_dfs (g->graph, &dummy_vis);
|
|
|
|
r_list_foreach (g->long_edges, it, e) {
|
|
const ANode *from = (ANode *)e->from->data;
|
|
const ANode *to = (ANode *)e->to->data;
|
|
int diff_layer = R_ABS (from->layer - to->layer);
|
|
RGraphNode *prev = e->from;
|
|
int i;
|
|
|
|
r_graph_del_edge (g->graph, e->from, e->to);
|
|
for (i = 1; i < diff_layer; ++i) {
|
|
ANode *n = ascii_node_new (R_TRUE);
|
|
RGraphNode *dummy;
|
|
|
|
if (!n) return;
|
|
n->layer = from->layer + i;
|
|
n->is_reversed = is_reversed (g, e);
|
|
n->w = 1;
|
|
dummy = r_graph_add_node (g->graph, n);
|
|
r_graph_add_edge (g->graph, prev, dummy);
|
|
prev = dummy;
|
|
}
|
|
r_graph_add_edge (g->graph, prev, e->to);
|
|
}
|
|
}
|
|
|
|
/* create layers and assign an initial ordering of the nodes into them */
|
|
static void create_layers (AGraph *g) {
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
RGraphNode *gn;
|
|
const RListIter *it;
|
|
ANode *n;
|
|
int i;
|
|
|
|
/* identify max layer */
|
|
g->n_layers = 0;
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
if (n->layer > g->n_layers)
|
|
g->n_layers = n->layer;
|
|
}
|
|
|
|
/* create a starting ordering of nodes for each layer */
|
|
g->n_layers++;
|
|
g->layers = R_NEWS0 (struct layer_t, g->n_layers);
|
|
|
|
graph_foreach_anode (nodes, it, gn, n)
|
|
g->layers[n->layer].n_nodes++;
|
|
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
g->layers[i].nodes = R_NEWS (RGraphNode *, g->layers[i].n_nodes);
|
|
g->layers[i].position = 0;
|
|
}
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
n->pos_in_layer = g->layers[n->layer].position;
|
|
g->layers[n->layer].nodes[g->layers[n->layer].position++] = gn;
|
|
}
|
|
}
|
|
|
|
/* layer-by-layer sweep */
|
|
/* it permutes each layer, trying to find the best ordering for each layer
|
|
* to minimize the number of crossing edges */
|
|
static void minimize_crossings (const AGraph *g) {
|
|
int i, cross_changed;
|
|
|
|
do {
|
|
cross_changed = R_FALSE;
|
|
|
|
for (i = 0; i < g->n_layers; ++i)
|
|
cross_changed |= layer_sweep (g->graph, g->layers, g->n_layers, i, R_TRUE);
|
|
} while (cross_changed);
|
|
|
|
do {
|
|
cross_changed = R_FALSE;
|
|
|
|
for (i = g->n_layers - 1; i >= 0; --i)
|
|
cross_changed |= layer_sweep (g->graph, g->layers, g->n_layers, i, R_FALSE);
|
|
} while (cross_changed);
|
|
}
|
|
|
|
static int find_dist (const struct dist_t *a, const struct dist_t *b) {
|
|
return a->from == b->from && a->to == b->to ? 0 : 1;
|
|
}
|
|
|
|
/* returns the distance between two nodes */
|
|
/* if the distance between two nodes were explicitly set, returns that;
|
|
* otherwise calculate the distance of two nodes on the same layer */
|
|
static int dist_nodes (const AGraph *g, const RGraphNode *a, const RGraphNode *b) {
|
|
struct dist_t d;
|
|
const ANode *aa, *ab;
|
|
RListIter *it;
|
|
int res = 0;
|
|
|
|
if (g->dists) {
|
|
d.from = a;
|
|
d.to = b;
|
|
it = r_list_find (g->dists, &d, (RListComparator)find_dist);
|
|
if (it) {
|
|
struct dist_t *old = (struct dist_t *)r_list_iter_get_data (it);
|
|
return old->dist;
|
|
}
|
|
}
|
|
|
|
aa = (ANode *)a->data;
|
|
ab = (ANode *)b->data;
|
|
if (aa->layer == ab->layer) {
|
|
int i;
|
|
|
|
res = 0;
|
|
for (i = aa->pos_in_layer; i < ab->pos_in_layer; ++i) {
|
|
const RGraphNode *cur = g->layers[aa->layer].nodes[i];
|
|
const RGraphNode *next = g->layers[aa->layer].nodes[i + 1];
|
|
const ANode *anext = (ANode *)next->data;
|
|
const ANode *acur = (ANode *)cur->data;
|
|
int found = R_FALSE;
|
|
|
|
if (g->dists) {
|
|
d.from = cur;
|
|
d.to = next;
|
|
it = r_list_find (g->dists, &d, (RListComparator)find_dist);
|
|
if (it) {
|
|
struct dist_t *old = (struct dist_t *)r_list_iter_get_data (it);
|
|
res += old->dist;
|
|
found = R_TRUE;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
int space = acur->is_dummy && anext->is_dummy ? 1 : HORIZONTAL_NODE_SPACING;
|
|
res += acur->w / 2 + anext->w / 2 + space;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* explictly set the distance between two nodes on the same layer */
|
|
static void set_dist_nodes (const AGraph *g, int l, int cur, int next) {
|
|
struct dist_t *d;
|
|
const RGraphNode *vi, *vip;
|
|
const ANode *avi, *avip;
|
|
|
|
if (!g->dists) return;
|
|
d = R_NEW (struct dist_t);
|
|
|
|
vi = g->layers[l].nodes[cur];
|
|
vip = g->layers[l].nodes[next];
|
|
avi = (ANode *)vi->data;
|
|
avip = (ANode *)vip->data;
|
|
|
|
d->from = vi;
|
|
d->to = vip;
|
|
d->dist = avip->x - avi->x;
|
|
r_list_push (g->dists, d);
|
|
}
|
|
|
|
static int is_valid_pos (const AGraph *g, int l, int pos) {
|
|
return pos >= 0 && pos < g->layers[l].n_nodes;
|
|
}
|
|
|
|
/* computes the set of vertical classes in the graph */
|
|
/* if v is an original node, L(v) = { v }
|
|
* if v is a dummy node, L(v) is the set of all the dummies node that belongs
|
|
* to the same long edge */
|
|
static Sdb *compute_vertical_nodes (const AGraph *g) {
|
|
Sdb *res = sdb_new0 ();
|
|
int i, j;
|
|
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
RGraphNode *gn = g->layers[i].nodes[j];
|
|
const RList *Ln = hash_get_rlist (res, gn);
|
|
const ANode *an = (ANode *)gn->data;
|
|
|
|
if (!Ln) {
|
|
RList *vert = r_list_new ();
|
|
hash_set (res, gn, vert);
|
|
if (an->is_dummy) {
|
|
RGraphNode *next = gn;
|
|
const ANode *anext = (ANode *)next->data;
|
|
|
|
while (next && anext->is_dummy) {
|
|
r_list_append (vert, next);
|
|
next = r_graph_nth_neighbour (g->graph, next, 0);
|
|
if (!next) break;
|
|
anext = (ANode *)next->data;
|
|
}
|
|
} else {
|
|
r_list_append (vert, gn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* computes left or right classes, used to place dummies node */
|
|
/* classes respect three properties:
|
|
* - v E C
|
|
* - w E C => L(v) is a subset of C
|
|
* - w E C, the s+(w) exists and is not in any class yet => s+(w) E C */
|
|
static RList **compute_classes (const AGraph *g, Sdb *v_nodes, int is_left, int *n_classes) {
|
|
int i, j, c;
|
|
RList **res = R_NEWS0 (RList *, g->n_layers);
|
|
RGraphNode *gn;
|
|
const RListIter *it;
|
|
ANode *n;
|
|
|
|
graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) {
|
|
n->class = -1;
|
|
}
|
|
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
c = i;
|
|
|
|
for (j = is_left ? 0 : g->layers[i].n_nodes - 1;
|
|
(is_left && j < g->layers[i].n_nodes) || (!is_left && j >= 0);
|
|
j = is_left ? j + 1 : j - 1) {
|
|
const RGraphNode *gj = g->layers[i].nodes[j];
|
|
const ANode *aj = (ANode *)gj->data;
|
|
|
|
if (aj->class == -1) {
|
|
const RList *laj = hash_get_rlist (v_nodes, gj);
|
|
|
|
if (!res[c])
|
|
res[c] = r_list_new ();
|
|
graph_foreach_anode (laj, it, gn, n) {
|
|
r_list_append (res[c], gn);
|
|
n->class = c;
|
|
}
|
|
} else {
|
|
c = aj->class;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n_classes)
|
|
*n_classes = g->n_layers;
|
|
return res;
|
|
}
|
|
|
|
static int cmp_dist (const size_t a, const size_t b) {
|
|
return (int)a < (int)b;
|
|
}
|
|
|
|
static RGraphNode *get_sibling (const AGraph *g, const ANode *n, int is_left, int is_adjust_class) {
|
|
RGraphNode *res = NULL;
|
|
int pos;
|
|
|
|
if ((is_left && is_adjust_class) || (!is_left && !is_adjust_class))
|
|
pos = n->pos_in_layer + 1;
|
|
else
|
|
pos = n->pos_in_layer - 1;
|
|
|
|
if (is_valid_pos (g, n->layer, pos))
|
|
res = g->layers[n->layer].nodes[pos];
|
|
return res;
|
|
}
|
|
|
|
static int adjust_class_val (const AGraph *g, const RGraphNode *gn,
|
|
const RGraphNode *sibl, Sdb *res, int is_left) {
|
|
if (is_left)
|
|
return hash_get_int (res, sibl) - hash_get_int (res, gn) - dist_nodes (g, gn, sibl);
|
|
else
|
|
return hash_get_int (res, gn) - hash_get_int (res, sibl) - dist_nodes (g, sibl, gn);
|
|
}
|
|
|
|
/* adjusts the position of previously placed left/right classes */
|
|
/* tries to place classes as close as possible */
|
|
static void adjust_class (const AGraph *g, int is_left,
|
|
RList **classes, Sdb *res, int c) {
|
|
const RGraphNode *gn;
|
|
const RListIter *it;
|
|
const ANode *an;
|
|
int dist, v, is_first = R_TRUE;
|
|
|
|
graph_foreach_anode (classes[c], it, gn, an) {
|
|
const RGraphNode *sibling;
|
|
const ANode *sibl_anode;
|
|
|
|
sibling = get_sibling (g, an, is_left, R_TRUE);
|
|
if (!sibling) continue;
|
|
sibl_anode = (ANode *)sibling->data;
|
|
if (sibl_anode->class == c) continue;
|
|
v = adjust_class_val (g, gn, sibling, res, is_left);
|
|
dist = is_first ? v : R_MIN (dist, v);
|
|
is_first = R_FALSE;
|
|
}
|
|
|
|
if (is_first) {
|
|
RList *heap = r_list_new ();
|
|
int len;
|
|
|
|
graph_foreach_anode (classes[c], it, gn, an) {
|
|
const RList *neigh = r_graph_all_neighbours (g->graph, gn);
|
|
const RGraphNode *gk;
|
|
const RListIter *itk;
|
|
const ANode *ak;
|
|
|
|
graph_foreach_anode (neigh, itk, gk, ak) {
|
|
if (ak->class < c)
|
|
r_list_append (heap, (void *)(size_t)(ak->x - an->x));
|
|
}
|
|
}
|
|
|
|
len = r_list_length (heap);
|
|
if (len == 0) {
|
|
dist = 0;
|
|
} else {
|
|
r_list_sort (heap, (RListComparator)cmp_dist);
|
|
dist = (int)(size_t)r_list_get_n (heap, len / 2);
|
|
}
|
|
|
|
r_list_free (heap);
|
|
}
|
|
|
|
graph_foreach_anode (classes[c], it, gn, an) {
|
|
int old_val = hash_get_int (res, gn);
|
|
int new_val = is_left ? old_val + dist : old_val - dist;
|
|
hash_set (res, gn, new_val);
|
|
}
|
|
}
|
|
|
|
static int place_nodes_val (const AGraph *g, const RGraphNode *gn,
|
|
const RGraphNode *sibl, Sdb *res, int is_left) {
|
|
if (is_left)
|
|
return hash_get_int (res, sibl) + dist_nodes (g, sibl, gn);
|
|
else
|
|
return hash_get_int (res, sibl) - dist_nodes (g, gn, sibl);
|
|
}
|
|
|
|
static int place_nodes_sel_p (int newval, int oldval, int is_first, int is_left) {
|
|
if (is_first)
|
|
return newval;
|
|
|
|
if (is_left)
|
|
return R_MAX (oldval, newval);
|
|
else
|
|
return R_MIN (oldval, newval);
|
|
}
|
|
|
|
/* places left/right the nodes of a class */
|
|
static void place_nodes (const AGraph *g, const RGraphNode *gn, int is_left,
|
|
Sdb *v_nodes, RList **classes, Sdb *res, Sdb *placed) {
|
|
const RList *lv = hash_get_rlist (v_nodes, gn);
|
|
int p, v, is_first = R_TRUE;
|
|
const RGraphNode *gk;
|
|
const RListIter *itk;
|
|
const ANode *ak;
|
|
|
|
graph_foreach_anode (lv, itk, gk, ak) {
|
|
const RGraphNode *sibling;
|
|
const ANode *sibl_anode;
|
|
|
|
sibling = get_sibling (g, ak, is_left, R_FALSE);
|
|
if (!sibling) continue;
|
|
sibl_anode = (ANode *)sibling->data;
|
|
if (ak->class == sibl_anode->class) {
|
|
if (!hash_get (placed, sibling))
|
|
place_nodes (g, sibling, is_left, v_nodes, classes, res, placed);
|
|
|
|
v = place_nodes_val (g, gk, sibling, res, is_left);
|
|
p = place_nodes_sel_p (v, p, is_first, is_left);
|
|
is_first = R_FALSE;
|
|
}
|
|
}
|
|
|
|
if (is_first)
|
|
p = is_left ? 0 : 50;
|
|
|
|
graph_foreach_anode (lv, itk, gk, ak) {
|
|
hash_set (res, gk, p);
|
|
hash_set (placed, gk, R_TRUE);
|
|
}
|
|
}
|
|
|
|
/* computes the position to the left/right of all the nodes */
|
|
static Sdb *compute_pos (const AGraph *g, int is_left, Sdb *v_nodes) {
|
|
Sdb *res, *placed;
|
|
RList **classes;
|
|
int n_classes, i;
|
|
|
|
classes = compute_classes (g, v_nodes, is_left, &n_classes);
|
|
if (!classes) return NULL;
|
|
|
|
res = sdb_new0 ();
|
|
placed = sdb_new0 ();
|
|
for (i = 0; i < n_classes; ++i) {
|
|
const RGraphNode *gn;
|
|
const RListIter *it;
|
|
|
|
r_list_foreach (classes[i], it, gn) {
|
|
if (!hash_get_rnode (placed, gn)) {
|
|
place_nodes (g, gn, is_left, v_nodes, classes, res, placed);
|
|
}
|
|
}
|
|
|
|
adjust_class (g, is_left, classes, res, i);
|
|
}
|
|
|
|
sdb_free (placed);
|
|
for (i = 0; i < n_classes; ++i) {
|
|
if (classes[i])
|
|
r_list_free (classes[i]);
|
|
}
|
|
free (classes);
|
|
return res;
|
|
}
|
|
|
|
/* calculates position of all nodes, but in particular dummies nodes */
|
|
/* computes two different placements (called "left"/"right") and set the final
|
|
* position of each node to the average of the values in the two placements */
|
|
static void place_dummies (const AGraph *g) {
|
|
const RList *nodes;
|
|
Sdb *xminus, *xplus, *vertical_nodes;
|
|
const RGraphNode *gn;
|
|
const RListIter *it;
|
|
ANode *n;
|
|
|
|
vertical_nodes = compute_vertical_nodes (g);
|
|
if (!vertical_nodes) return;
|
|
xminus = compute_pos (g, R_TRUE, vertical_nodes);
|
|
if (!xminus) goto xminus_err;
|
|
xplus = compute_pos (g, R_FALSE, vertical_nodes);
|
|
if (!xplus) goto xplus_err;
|
|
|
|
nodes = r_graph_get_nodes (g->graph);
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
n->x = (hash_get_int (xminus, gn) + hash_get_int (xplus, gn)) / 2;
|
|
}
|
|
|
|
sdb_free (xplus);
|
|
xplus_err:
|
|
sdb_free (xminus);
|
|
xminus_err:
|
|
sdb_free (vertical_nodes);
|
|
}
|
|
|
|
static RGraphNode *get_right_dummy (const AGraph *g, const RGraphNode *n) {
|
|
const ANode *an = (ANode *)n->data;
|
|
int k, layer = an->layer;
|
|
|
|
for (k = an->pos_in_layer + 1; k < g->layers[layer].n_nodes; ++k) {
|
|
RGraphNode *gk = g->layers[layer].nodes[k];
|
|
const ANode *ak = (ANode *)gk->data;
|
|
|
|
if (ak->is_dummy)
|
|
return gk;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* returns true if all nodes on the right of n are dummies and reversed */
|
|
static int all_reversed_right (const AGraph *g, const RGraphNode *n) {
|
|
const ANode *an;
|
|
int k, layer;
|
|
|
|
if (!n) return R_FALSE;
|
|
|
|
an = (ANode *)n->data;
|
|
layer = an->layer;
|
|
for (k = an->pos_in_layer; k < g->layers[layer].n_nodes; ++k) {
|
|
const RGraphNode *gk = g->layers[layer].nodes[k];
|
|
const ANode *ak = (ANode *)gk->data;
|
|
|
|
if (!ak->is_reversed)
|
|
return R_FALSE;
|
|
}
|
|
|
|
return R_TRUE;
|
|
}
|
|
|
|
static void adjust_directions (const AGraph *g, int i, int from_up, Sdb *D, Sdb *P) {
|
|
const RGraphNode *vm = NULL, *wm = NULL;
|
|
const ANode *vma = NULL, *wma = NULL;
|
|
int j, d = from_up ? 1 : -1;
|
|
|
|
if (i + d < 0 || i + d >= g->n_layers) return;
|
|
|
|
for (j = 0; j < g->layers[i + d].n_nodes; ++j) {
|
|
const RGraphNode *wp, *vp = g->layers[i + d].nodes[j];
|
|
const ANode *wpa, *vpa = (ANode *)vp->data;
|
|
|
|
if (!vpa->is_dummy) continue;
|
|
if (from_up)
|
|
wp = r_list_get_n (r_graph_innodes (g->graph, vp), 0);
|
|
else
|
|
wp = r_graph_nth_neighbour (g->graph, vp, 0);
|
|
wpa = (ANode *)wp->data;
|
|
if (!wpa->is_dummy) continue;
|
|
|
|
if (vm) {
|
|
int p = hash_get_int (P, wm);
|
|
int k;
|
|
|
|
for (k = wma->pos_in_layer + 1; k < wpa->pos_in_layer; ++k) {
|
|
const RGraphNode *w = g->layers[wma->layer].nodes[k];
|
|
const ANode *aw = (ANode *)w->data;
|
|
|
|
if (aw->is_dummy)
|
|
p &= hash_get_int (P, w);
|
|
}
|
|
if (p) {
|
|
hash_set (D, vm, from_up);
|
|
for (k = vma->pos_in_layer + 1; k < vpa->pos_in_layer; ++k) {
|
|
const RGraphNode *v = g->layers[vma->layer].nodes[k];
|
|
const ANode *av = (ANode *)v->data;
|
|
|
|
if (av->is_dummy)
|
|
hash_set (D, v, from_up);
|
|
}
|
|
}
|
|
}
|
|
|
|
vm = vp;
|
|
wm = wp;
|
|
vma = (ANode *)vm->data;
|
|
wma = (ANode *)wm->data;
|
|
}
|
|
}
|
|
|
|
/* find a placement for a single node */
|
|
static void place_single (const AGraph *g, int l, const RGraphNode *bm,
|
|
const RGraphNode *bp, int from_up, int va) {
|
|
const RGraphNode *gk, *v = g->layers[l].nodes[va];
|
|
const ANode *ak;
|
|
ANode *av = (ANode *)v->data;
|
|
const RList *neigh;
|
|
const RListIter *itk;
|
|
int len;
|
|
|
|
if (from_up)
|
|
neigh = r_graph_innodes (g->graph, v);
|
|
else
|
|
neigh = r_graph_get_neighbours (g->graph, v);
|
|
|
|
len = r_list_length (neigh);
|
|
if (len == 0)
|
|
return;
|
|
|
|
int sum_x = 0;
|
|
graph_foreach_anode (neigh, itk, gk, ak) {
|
|
if (ak->is_reversed) {
|
|
len--;
|
|
continue;
|
|
}
|
|
|
|
sum_x += ak->x;
|
|
}
|
|
|
|
if (len == 0)
|
|
return;
|
|
|
|
av->x = sum_x / len;
|
|
if (bm) {
|
|
const ANode *bma = (ANode *)bm->data;
|
|
av->x = R_MAX (av->x, bma->x + dist_nodes (g, bm, v));
|
|
}
|
|
if (bp && !all_reversed_right (g, bp)) {
|
|
const ANode *bpa = (ANode *)bp->data;
|
|
av->x = R_MIN (av->x, bpa->x - dist_nodes (g, v, bp));
|
|
}
|
|
}
|
|
|
|
static int RM_listcmp (const struct len_pos_t *a, const struct len_pos_t *b) {
|
|
return a->pos < b->pos;
|
|
}
|
|
|
|
static int RP_listcmp (const struct len_pos_t *a, const struct len_pos_t *b) {
|
|
return a->pos >= b->pos;
|
|
}
|
|
|
|
static void collect_changes (const AGraph *g, int l, const RGraphNode *b,
|
|
int from_up, int s, int e, RList *list, int is_left) {
|
|
const RGraphNode *vt = g->layers[l].nodes[e - 1];
|
|
const RGraphNode *vtp = g->layers[l].nodes[s];
|
|
RListComparator lcmp;
|
|
struct len_pos_t *cx;
|
|
int i;
|
|
|
|
lcmp = is_left ? (RListComparator)RM_listcmp : (RListComparator)RP_listcmp;
|
|
|
|
for (i = is_left ? s : e - 1;
|
|
(is_left && i < e) || (!is_left && i >= s);
|
|
i = is_left ? i + 1 : i - 1) {
|
|
const RGraphNode *v, *vi = g->layers[l].nodes[i];
|
|
const ANode *av, *avi = (ANode *)vi->data;
|
|
const RList *neigh;
|
|
const RListIter *it;
|
|
int c = 0;
|
|
|
|
if (from_up)
|
|
neigh = r_graph_innodes (g->graph, vi);
|
|
else
|
|
neigh = r_graph_get_neighbours (g->graph, vi);
|
|
|
|
graph_foreach_anode (neigh, it, v, av) {
|
|
if ((is_left && av->x >= avi->x) || (!is_left && av->x <= avi->x)) {
|
|
c++;
|
|
} else {
|
|
cx = R_NEW (struct len_pos_t);
|
|
|
|
c--;
|
|
cx->len = 2;
|
|
cx->pos = av->x;
|
|
if (is_left)
|
|
cx->pos += dist_nodes (g, vi, vt);
|
|
else
|
|
cx->pos -= dist_nodes (g, vtp, vi);
|
|
r_list_add_sorted (list, cx, lcmp);
|
|
}
|
|
}
|
|
|
|
cx = R_NEW (struct len_pos_t);
|
|
cx->len = c;
|
|
cx->pos = avi->x;
|
|
if (is_left)
|
|
cx->pos += dist_nodes (g, vi, vt);
|
|
else
|
|
cx->pos -= dist_nodes (g, vtp, vi);
|
|
r_list_add_sorted (list, cx, lcmp);
|
|
}
|
|
|
|
if (b) {
|
|
const ANode *ab = (ANode *)b->data;
|
|
|
|
cx = R_NEW (struct len_pos_t);
|
|
cx->len = is_left ? INT_MAX : INT_MIN;
|
|
cx->pos = ab->x;
|
|
if (is_left)
|
|
cx->pos += dist_nodes (g, b, vt);
|
|
else
|
|
cx->pos -= dist_nodes (g, vtp, b);
|
|
r_list_add_sorted (list, cx, lcmp);
|
|
}
|
|
}
|
|
|
|
static void combine_sequences (const AGraph *g, int l,
|
|
const RGraphNode *bm, const RGraphNode *bp,
|
|
int from_up, int a, int r) {
|
|
RList *Rm = r_list_new (), *Rp = r_list_new ();
|
|
const RGraphNode *vt, *vtp;
|
|
ANode *at, *atp;
|
|
int rm, rp, t, m, i;
|
|
|
|
t = (a + r) / 2;
|
|
vt = g->layers[l].nodes[t - 1];
|
|
vtp = g->layers[l].nodes[t];
|
|
at = (ANode *)vt->data;
|
|
atp = (ANode *)vtp->data;
|
|
|
|
collect_changes (g, l, bm, from_up, a, t, Rm, R_TRUE);
|
|
collect_changes (g, l, bp, from_up, t, r, Rp, R_FALSE);
|
|
rm = rp = 0;
|
|
|
|
m = dist_nodes (g, vt, vtp);
|
|
while (atp->x - at->x < m) {
|
|
if (rm < rp) {
|
|
if (r_list_empty (Rm)) {
|
|
at->x = atp->x - m;
|
|
} else {
|
|
struct len_pos_t *cx = (struct len_pos_t *)r_list_pop (Rm);
|
|
rm = rm + cx->len;
|
|
at->x = R_MAX (cx->pos, atp->x - m);
|
|
free (cx);
|
|
}
|
|
} else {
|
|
if (r_list_empty (Rp)) {
|
|
atp->x = at->x + m;
|
|
} else {
|
|
struct len_pos_t *cx = (struct len_pos_t *)r_list_pop (Rp);
|
|
rp = rp + cx->len;
|
|
atp->x = R_MIN (cx->pos, at->x + m);
|
|
free (cx);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = t - 2; i >= a; --i) {
|
|
const RGraphNode *gv = g->layers[l].nodes[i];
|
|
ANode *av = (ANode *)gv->data;
|
|
|
|
av->x = R_MIN (av->x, at->x - dist_nodes (g, gv, vt));
|
|
}
|
|
|
|
for (i = t + 1; i < r; ++i) {
|
|
const RGraphNode *gv = g->layers[l].nodes[i];
|
|
ANode *av = (ANode *)gv->data;
|
|
|
|
av->x = R_MAX (av->x, atp->x + dist_nodes (g, vtp, gv));
|
|
}
|
|
}
|
|
|
|
/* places a sequence of consecutive original nodes */
|
|
/* it tries to minimize the distance between each node in the sequence and its
|
|
* neighbours in the "previous" layer. Those neighbours are considered as
|
|
* "fixed". The previous layer depends on the direction used during the layers
|
|
* traversal */
|
|
static void place_sequence (const AGraph *g, int l,
|
|
const RGraphNode *bm, const RGraphNode *bp,
|
|
int from_up, int va, int vr) {
|
|
int vt;
|
|
|
|
if (vr == va + 1) {
|
|
place_single (g, l, bm, bp, from_up, va);
|
|
} else if (vr > va + 1) {
|
|
vt = (vr + va) / 2;
|
|
place_sequence (g, l, bm, bp, from_up, va, vt);
|
|
place_sequence (g, l, bm, bp, from_up, vt, vr);
|
|
combine_sequences (g, l, bm, bp, from_up, va, vr);
|
|
}
|
|
}
|
|
|
|
/* if all nodes to the right of right_pos are reversed, shift them to make the
|
|
* placement feasible and larger */
|
|
static void shift_right_dummies (const AGraph *g, int l, int right_pos) {
|
|
const RGraphNode *vr, *bp;
|
|
const ANode *ar, *abp;
|
|
|
|
if (!is_valid_pos (g, l, right_pos)) return;
|
|
vr = g->layers[l].nodes[right_pos];
|
|
ar = (ANode *)vr->data;
|
|
if (ar->is_dummy) return;
|
|
|
|
if (!is_valid_pos (g, l, right_pos + 1)) return;
|
|
bp = g->layers[l].nodes[right_pos + 1];
|
|
abp = (ANode *)bp->data;
|
|
if (!abp->is_dummy) return;
|
|
|
|
if (all_reversed_right (g, bp)) {
|
|
int k;
|
|
|
|
for (k = right_pos + 1; k < g->layers[l].n_nodes; ++k) {
|
|
RGraphNode *vk = g->layers[l].nodes[k];
|
|
ANode *ak = (ANode *)vk->data;
|
|
int newv = ar->x + dist_nodes (g, vr, vk);
|
|
|
|
if (newv > ak->x) {
|
|
const RGraphNode *vj = vk;
|
|
ANode *aj = ak;
|
|
|
|
while (vj && aj && aj->is_dummy && aj->is_reversed) {
|
|
aj->x = newv;
|
|
|
|
vj = r_list_get_n (r_graph_innodes (g->graph, vj), 0);
|
|
aj = (ANode *)vj->data;
|
|
}
|
|
|
|
vj = vk;
|
|
aj = ak;
|
|
while (vj && aj && aj->is_dummy && aj->is_reversed) {
|
|
aj->x = newv;
|
|
|
|
vj = r_graph_nth_neighbour (g->graph, vj, 0);
|
|
aj = (ANode *)vj->data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* finds the placements of nodes while traversing the graph in the given
|
|
* direction */
|
|
/* places all the sequences of consecutive original nodes in each layer. */
|
|
static void original_traverse_l (const AGraph *g, Sdb *D, Sdb *P, int from_up) {
|
|
int i, k, va, vr;
|
|
|
|
for (i = from_up ? 0 : g->n_layers - 1;
|
|
(from_up && i < g->n_layers) || (!from_up && i >= 0);
|
|
i = from_up ? i + 1 : i - 1) {
|
|
int j;
|
|
const RGraphNode *bm = NULL;
|
|
const ANode *bma = NULL;
|
|
|
|
j = 0;
|
|
while (j < g->layers[i].n_nodes && !bm) {
|
|
const RGraphNode *gn = g->layers[i].nodes[j];
|
|
const ANode *an = (ANode *)gn->data;
|
|
|
|
if (an->is_dummy) {
|
|
va = 0;
|
|
vr = j;
|
|
bm = gn;
|
|
bma = an;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
if (!bm) {
|
|
va = 0;
|
|
vr = g->layers[i].n_nodes;
|
|
}
|
|
|
|
place_sequence (g, i, NULL, bm, from_up, va, vr);
|
|
for (k = va; k < vr - 1; ++k)
|
|
set_dist_nodes (g, i, k, k + 1);
|
|
|
|
if (is_valid_pos (g, i, vr - 1) && bm)
|
|
set_dist_nodes (g, i, vr - 1, bma->pos_in_layer);
|
|
shift_right_dummies (g, i, vr - 1);
|
|
|
|
while (bm) {
|
|
const RGraphNode *bp = get_right_dummy (g, bm);
|
|
const ANode *bpa = NULL;
|
|
bma = (ANode *)bm->data;
|
|
|
|
if (!bp) {
|
|
va = bma->pos_in_layer + 1;
|
|
vr = g->layers[bma->layer].n_nodes;
|
|
place_sequence (g, i, bm, NULL, from_up, va, vr);
|
|
for (k = va; k < vr - 1; ++k)
|
|
set_dist_nodes (g, i, k, k + 1);
|
|
|
|
if (is_valid_pos (g, i, va))
|
|
set_dist_nodes (g, i, bma->pos_in_layer, va);
|
|
shift_right_dummies (g, i, vr - 1);
|
|
} else if (hash_get_int (D, bm) == from_up) {
|
|
bpa = (ANode *)bp->data;
|
|
va = bma->pos_in_layer + 1;
|
|
vr = bpa->pos_in_layer;
|
|
place_sequence (g, i, bm, bp, from_up, va, vr);
|
|
hash_set (P, bm, R_TRUE);
|
|
shift_right_dummies (g, i, vr - 1);
|
|
}
|
|
|
|
bm = bp;
|
|
}
|
|
|
|
adjust_directions (g, i, from_up, D, P);
|
|
}
|
|
}
|
|
|
|
/* computes a final position of original nodes, considering dummies nodes as
|
|
* fixed */
|
|
/* set the node placements traversing the graph downward and then upward */
|
|
static void place_original (AGraph *g) {
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
Sdb *D, *P;
|
|
const RGraphNode *gn;
|
|
const RListIter *itn;
|
|
const ANode *an;
|
|
|
|
D = sdb_new0 ();
|
|
P = sdb_new0 ();
|
|
g->dists = r_list_new ();
|
|
g->dists->free = (RListFree)free;
|
|
|
|
graph_foreach_anode (nodes, itn, gn, an) {
|
|
if (!an->is_dummy) continue;
|
|
const RGraphNode *right_v = get_right_dummy (g, gn);
|
|
if (right_v) {
|
|
const ANode *right = (ANode *)right_v->data;
|
|
|
|
hash_set (D, gn, 0);
|
|
int dt_eq = right->x - an->x == dist_nodes (g, gn, right_v);
|
|
hash_set (P, gn, dt_eq);
|
|
}
|
|
}
|
|
|
|
original_traverse_l (g, D, P, R_TRUE);
|
|
original_traverse_l (g, D, P, R_FALSE);
|
|
|
|
r_list_free (g->dists);
|
|
g->dists = NULL;
|
|
sdb_free (P);
|
|
sdb_free (D);
|
|
}
|
|
|
|
static void restore_original_edges (const AGraph *g) {
|
|
const RListIter *it;
|
|
const RGraphEdge *e;
|
|
|
|
r_list_foreach (g->long_edges, it, e) {
|
|
r_graph_add_edge (g->graph, e->from, e->to);
|
|
}
|
|
|
|
r_list_foreach (g->back_edges, it, e) {
|
|
r_graph_del_edge (g->graph, e->to, e->from);
|
|
r_graph_add_edge (g->graph, e->from, e->to);
|
|
}
|
|
}
|
|
|
|
static void remove_dummy_nodes (const AGraph *g) {
|
|
RGraphNode *gn;
|
|
const RListIter *it;
|
|
const ANode *n;
|
|
RList *toremove = r_list_new ();
|
|
|
|
graph_foreach_anode (r_graph_get_nodes (g->graph), it, gn, n) {
|
|
if (n->is_dummy) {
|
|
r_list_append (toremove, gn);
|
|
}
|
|
}
|
|
|
|
r_list_foreach (toremove, it, gn) {
|
|
r_graph_del_node (g->graph, gn);
|
|
}
|
|
|
|
r_list_free (toremove);
|
|
}
|
|
|
|
/* 1) trasform the graph into a DAG
|
|
* 2) partition the nodes in layers
|
|
* 3) split long edges that traverse multiple layers
|
|
* 4) reorder nodes in each layer to reduce the number of edge crossing
|
|
* 5) assign x and y coordinates to each node
|
|
* 6) restore the original graph, with long edges and cycles */
|
|
static void set_layout_bb(AGraph *g) {
|
|
int i, j, k;
|
|
|
|
remove_cycles (g);
|
|
assign_layers (g);
|
|
create_dummy_nodes (g);
|
|
create_layers (g);
|
|
minimize_crossings (g);
|
|
|
|
/* identify row height */
|
|
for (i = 0; i < g->n_layers; i++) {
|
|
int rh = 0;
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
const ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
if (n->h > rh)
|
|
rh = n->h;
|
|
}
|
|
g->layers[i].height = rh;
|
|
}
|
|
|
|
/* x-coordinate assignment: algorithm based on:
|
|
* A Fast Layout Algorithm for k-Level Graphs
|
|
* by C. Buchheim, M. Junger, S. Leipert */
|
|
place_dummies (g);
|
|
place_original (g);
|
|
|
|
/* vertical align */
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
n->y = 1;
|
|
for (k = 0; k < n->layer; ++k) {
|
|
n->y += g->layers[k].height + VERTICAL_NODE_SPACING;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* finalize x coordinate */
|
|
for (i = 0; i < g->n_layers; ++i) {
|
|
for (j = 0; j < g->layers[i].n_nodes; ++j) {
|
|
ANode *n = (ANode *)(g->layers[i].nodes[j]->data);
|
|
n->x -= n->w / 2;
|
|
}
|
|
}
|
|
|
|
restore_original_edges (g);
|
|
remove_dummy_nodes (g);
|
|
|
|
/* free all temporary structures used during layout */
|
|
for (i = 0; i < g->n_layers; ++i)
|
|
free (g->layers[i].nodes);
|
|
free (g->layers);
|
|
r_list_free (g->long_edges);
|
|
r_list_free (g->back_edges);
|
|
}
|
|
|
|
static void set_layout_callgraph(AGraph *g) {
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
RGraphNode *gn;
|
|
RListIter *it;
|
|
ANode *prev_n = NULL, *n;
|
|
int y = 5, x = 20;
|
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
// wrap to width 'w'
|
|
if (prev_n && n->x < prev_n->x) {
|
|
y += 10;
|
|
x = 0;
|
|
}
|
|
n->x = x;
|
|
n->y = prev_n ? y : 2;
|
|
x += 30;
|
|
prev_n = n;
|
|
}
|
|
}
|
|
|
|
/* build the RGraph inside the AGraph g, starting from the Basic Blocks */
|
|
static int get_bbnodes(AGraph *g) {
|
|
RAnalBlock *bb;
|
|
RListIter *iter;
|
|
Sdb *g_nodes = sdb_new0 ();
|
|
if (!g_nodes) return R_FALSE;
|
|
|
|
r_list_foreach (g->fcn->bbs, iter, bb) {
|
|
RGraphNode *gn;
|
|
ANode *node;
|
|
|
|
if (bb->addr == UT64_MAX)
|
|
continue;
|
|
|
|
node = ascii_node_new (R_FALSE);
|
|
if (!node) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
|
|
if (g->is_simple_mode) {
|
|
node->text = r_core_cmd_strf (g->core,
|
|
"pI %d @ 0x%08"PFMT64x, bb->size, bb->addr);
|
|
}else {
|
|
node->text = r_core_cmd_strf (g->core,
|
|
"pDi %d @ 0x%08"PFMT64x, bb->size, bb->addr);
|
|
}
|
|
node->addr = bb->addr;
|
|
|
|
gn = r_graph_add_node (g->graph, node);
|
|
if (!gn) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
hash_set (g_nodes, bb->addr, gn);
|
|
}
|
|
|
|
r_list_foreach (g->fcn->bbs, iter, bb) {
|
|
RGraphNode *u, *v;
|
|
if (bb->addr == UT64_MAX)
|
|
continue;
|
|
|
|
u = hash_get_rnode (g_nodes, bb->addr);
|
|
if (bb->jump != UT64_MAX) {
|
|
v = hash_get_rnode (g_nodes, bb->jump);
|
|
r_graph_add_edge (g->graph, u, v);
|
|
}
|
|
if (bb->fail != UT64_MAX) {
|
|
v = hash_get_rnode (g_nodes, bb->fail);
|
|
r_graph_add_edge (g->graph, u, v);
|
|
}
|
|
}
|
|
|
|
sdb_free (g_nodes);
|
|
return R_TRUE;
|
|
}
|
|
|
|
/* build the RGraph inside the AGraph g, starting from the Call Graph
|
|
* information */
|
|
static int get_cgnodes(AGraph *g) {
|
|
#if FCN_OLD
|
|
Sdb *g_nodes = sdb_new0 ();
|
|
RGraphNode *fcn_gn;
|
|
RListIter *iter;
|
|
RAnalRef *ref;
|
|
ANode *node;
|
|
char *code;
|
|
|
|
node = ascii_node_new (R_FALSE);
|
|
if (!node) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
node->text = strdup ("");
|
|
node->addr = g->fcn->addr;
|
|
node->x = 10;
|
|
node->y = 3;
|
|
fcn_gn = r_graph_add_node (g->graph, node);
|
|
if (!fcn_gn) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
hash_set (g_nodes, g->fcn->addr, fcn_gn);
|
|
|
|
r_list_foreach (g->fcn->refs, iter, ref) {
|
|
/* XXX: something is broken, why there are duplicated
|
|
* nodes here?! goto check fcn->refs!! */
|
|
/* avoid dups wtf */
|
|
RGraphNode *gn;
|
|
gn = hash_get_rnode (g_nodes, ref->addr);
|
|
if (gn) continue;
|
|
|
|
RFlagItem *fi = r_flag_get_at (g->core->flags, ref->addr);
|
|
node = ascii_node_new (R_FALSE);
|
|
if (!node) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
if (fi) {
|
|
node->text = strdup (fi->name);
|
|
node->text = r_str_concat (node->text, ":\n");
|
|
} else {
|
|
node->text = strdup ("");
|
|
}
|
|
code = r_core_cmd_strf (g->core,
|
|
"pi 4 @ 0x%08"PFMT64x, ref->addr);
|
|
node->text = r_str_concat (node->text, code);
|
|
node->text = r_str_concat (node->text, "...\n");
|
|
node->addr = ref->addr;
|
|
node->x = 10;
|
|
node->y = 10;
|
|
free (code);
|
|
gn = r_graph_add_node (g->graph, node);
|
|
if (!gn) {
|
|
sdb_free (g_nodes);
|
|
return R_FALSE;
|
|
}
|
|
hash_set (g_nodes, ref->addr, gn);
|
|
|
|
r_graph_add_edge (g->graph, fcn_gn, gn);
|
|
}
|
|
|
|
sdb_free (g_nodes);
|
|
#else
|
|
eprintf ("Must be sdbized\n");
|
|
#endif
|
|
|
|
return R_TRUE;
|
|
}
|
|
|
|
static int reload_nodes(AGraph *g) {
|
|
int ret;
|
|
|
|
if (g->is_callgraph) {
|
|
ret = get_cgnodes(g);
|
|
if (!ret)
|
|
return R_FALSE;
|
|
} else {
|
|
ret = get_bbnodes(g);
|
|
if (!ret)
|
|
return R_FALSE;
|
|
}
|
|
|
|
update_node_dimension(g->graph, g->is_small_nodes, g->zoom);
|
|
return R_TRUE;
|
|
}
|
|
|
|
static void update_seek(RConsCanvas *can, ANode *n, int force) {
|
|
int x, y, w, h;
|
|
int doscroll = R_FALSE;
|
|
|
|
if (!n) return;
|
|
|
|
x = n->x + can->sx;
|
|
y = n->y + can->sy;
|
|
w = can->w;
|
|
h = can->h;
|
|
|
|
doscroll = force || y < 0 || y + 5 > h || x + 5 > w || x + n->w + 5 < 0;
|
|
|
|
if (doscroll) {
|
|
// top-left
|
|
can->sy = -n->y + BORDER;
|
|
can->sx = -n->x + BORDER;
|
|
// center
|
|
can->sy = -n->y + BORDER + (h / 8);
|
|
can->sx = -n->x + BORDER + (w / 4);
|
|
}
|
|
}
|
|
|
|
static int is_near (const ANode *n, int x, int y, int is_next) {
|
|
if (is_next)
|
|
return (n->y == y && n->x > x) || n->y > y;
|
|
else
|
|
return (n->y == y && n->x < x) || n->y < y;
|
|
}
|
|
|
|
static const RGraphNode *find_near_of (const AGraph *g, const RGraphNode *cur,
|
|
int is_next) {
|
|
/* XXX: it's slow */
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
const RListIter *it;
|
|
const RGraphNode *gn, *resgn = NULL;
|
|
const ANode *n, *acur = cur ? get_anode(cur) : NULL;
|
|
int default_v = is_next ? INT_MIN : INT_MAX;
|
|
int start_y = acur ? acur->y : default_v;
|
|
int start_x = acur ? acur->x : default_v;
|
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
if (is_near (n, start_x, start_y, is_next)) {
|
|
const ANode *resn;
|
|
|
|
if (!resgn) {
|
|
resgn = gn;
|
|
continue;
|
|
}
|
|
|
|
resn = get_anode (resgn);
|
|
if ((is_next && resn->y > n->y) || (!is_next && resn->y < n->y))
|
|
resgn = gn;
|
|
else if ((is_next && resn->y == n->y && resn->x > n->x) ||
|
|
(!is_next && resn->y == n->y && resn->x < n->x))
|
|
resgn = gn;
|
|
}
|
|
}
|
|
|
|
if (!resgn && cur)
|
|
resgn = find_near_of (g, NULL, is_next);
|
|
|
|
return resgn;
|
|
}
|
|
|
|
static void agraph_set_layout(AGraph *g) {
|
|
if (g->is_callgraph)
|
|
set_layout_callgraph(g);
|
|
else
|
|
set_layout_bb(g);
|
|
|
|
g->curnode = find_near_of (g, NULL, R_TRUE);
|
|
}
|
|
|
|
/* set the willing to center the screen on a particular node */
|
|
static void agraph_update_seek(AGraph *g, ANode *n, int force) {
|
|
g->update_seek_on = n;
|
|
g->force_update_seek = force;
|
|
}
|
|
|
|
static void agraph_free(AGraph *g) {
|
|
r_graph_free (g->graph);
|
|
r_stack_free (g->history);
|
|
free(g);
|
|
}
|
|
|
|
static void agraph_print_node(AGraph *g, ANode *n) {
|
|
const int cur = get_anode (g->curnode) == n;
|
|
|
|
if (g->is_small_nodes)
|
|
small_ANode_print(g, n, cur);
|
|
else
|
|
normal_ANode_print(g, n, cur);
|
|
}
|
|
|
|
static void agraph_print_nodes(AGraph *g) {
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
RGraphNode *gn;
|
|
RListIter *it;
|
|
ANode *n;
|
|
|
|
graph_foreach_anode (nodes, it, gn, n) {
|
|
if (gn != g->curnode)
|
|
agraph_print_node(g, n);
|
|
}
|
|
|
|
/* draw current node now to make it appear on top */
|
|
agraph_print_node (g, get_anode(g->curnode));
|
|
}
|
|
|
|
/* print an edge between two nodes.
|
|
* nth: specifies if the edge is the true(1)/false(2) branch or if it's the
|
|
* only edge for that node(0), so that a different style will be applied
|
|
* to the drawn line */
|
|
static void agraph_print_edge(AGraph *g, ANode *a, ANode *b, int nth) {
|
|
int x, y, x2, y2;
|
|
int xinc = 3 + 2 * (nth + 1);
|
|
x = a->x + xinc;
|
|
y = a->y + a->h;
|
|
x2 = b->x + xinc;
|
|
y2 = b->y;
|
|
if (a == b) {
|
|
x2 = a->x;
|
|
y2 = y - 3;
|
|
}
|
|
if (nth > 1)
|
|
nth = 1;
|
|
switch (nth) {
|
|
case 0: L1 (x, y, x2, y2); break;
|
|
case 1: L2 (x, y, x2, y2); break;
|
|
case -1: L (x, y, x2, y2); break;
|
|
}
|
|
}
|
|
|
|
static void agraph_print_edges(AGraph *g) {
|
|
const RList *nodes = r_graph_get_nodes (g->graph);
|
|
RGraphNode *gn, *gv;
|
|
RListIter *it, *itn;
|
|
ANode *u, *v;
|
|
|
|
graph_foreach_anode (nodes, it, gn, u) {
|
|
const RList *neighbours = r_graph_get_neighbours (g->graph, gn);
|
|
const int exit_edges = r_list_length (neighbours);
|
|
int nth = 0;
|
|
|
|
graph_foreach_anode (neighbours, itn, gv, v) {
|
|
int cur_nth = nth;
|
|
if (g->is_callgraph) {
|
|
/* hack: we don't support more than two exit edges from a node
|
|
* yet, so set nth to zero, to make every edge appears as the
|
|
* "true" edge of the node */
|
|
cur_nth = 0;
|
|
} else if (exit_edges == 1) {
|
|
cur_nth = -1;
|
|
}
|
|
agraph_print_edge (g, u, v, cur_nth);
|
|
nth++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void agraph_toggle_small_nodes(AGraph *g) {
|
|
g->is_small_nodes = !g->is_small_nodes;
|
|
g->need_update_dim = R_TRUE;
|
|
g->need_set_layout = R_TRUE;
|
|
}
|
|
|
|
static void agraph_toggle_simple_mode(AGraph *g) {
|
|
g->is_simple_mode = !g->is_simple_mode;
|
|
g->need_reload_nodes = R_TRUE;
|
|
}
|
|
|
|
static void agraph_toggle_callgraph(AGraph *g) {
|
|
g->is_callgraph = !g->is_callgraph;
|
|
g->need_reload_nodes = R_TRUE;
|
|
}
|
|
|
|
static void agraph_set_zoom (AGraph *g, int v) {
|
|
g->is_small_nodes = v <= 0;
|
|
g->zoom = R_MAX (0, v);
|
|
g->need_update_dim = R_TRUE;
|
|
g->need_set_layout = R_TRUE;
|
|
}
|
|
|
|
/* reload all the info in the nodes, depending on the type of the graph
|
|
* (callgraph, CFG, etc.), set the default layout for these nodes and center
|
|
* the screen on the selected one */
|
|
static int agraph_reload_nodes(AGraph *g) {
|
|
int ret;
|
|
|
|
r_graph_reset (g->graph);
|
|
ret = reload_nodes(g);
|
|
if (!ret)
|
|
return R_FALSE;
|
|
agraph_set_layout(g);
|
|
g->update_seek_on = get_anode(g->curnode);
|
|
return R_TRUE;
|
|
}
|
|
|
|
static void follow_nth(AGraph *g, int nth) {
|
|
const RGraphNode *cn = r_graph_nth_neighbour (g->graph, g->curnode, nth);
|
|
if (cn) {
|
|
history_push (g->history, g->curnode);
|
|
g->curnode = cn;
|
|
}
|
|
}
|
|
|
|
static void agraph_follow_true(AGraph *g) {
|
|
follow_nth(g, 0);
|
|
agraph_update_seek(g, get_anode(g->curnode), R_FALSE);
|
|
}
|
|
|
|
static void agraph_follow_false(AGraph *g) {
|
|
follow_nth(g, 1);
|
|
agraph_update_seek(g, get_anode(g->curnode), R_FALSE);
|
|
}
|
|
|
|
/* go back in the history of selected nodes, if we can */
|
|
static void agraph_undo_node(AGraph *g) {
|
|
const RGraphNode *p = history_pop (g->history);
|
|
if (p) {
|
|
g->curnode = p;
|
|
agraph_update_seek (g, p->data, R_FALSE);
|
|
}
|
|
}
|
|
|
|
/* pushes the current node in the history and makes g->curnode the next node in
|
|
* the order given by r_graph_get_nodes */
|
|
static void agraph_next_node(AGraph *g) {
|
|
history_push (g->history, g->curnode);
|
|
g->curnode = find_near_of (g, g->curnode, R_TRUE);
|
|
agraph_update_seek (g, get_anode(g->curnode), R_FALSE);
|
|
}
|
|
|
|
/* pushes the current node in the history and makes g->curnode the prev node in
|
|
* the order given by r_graph_get_nodes */
|
|
static void agraph_prev_node(AGraph *g) {
|
|
history_push (g->history, g->curnode);
|
|
g->curnode = find_near_of (g, g->curnode, R_FALSE);
|
|
agraph_update_seek (g, get_anode(g->curnode), R_FALSE);
|
|
}
|
|
|
|
static int agraph_refresh(struct agraph_refresh_data *grd) {
|
|
char title[TITLE_LEN];
|
|
AGraph *g = grd->g;
|
|
const int fs = grd->fs;
|
|
int h, w = r_cons_get_size (&h);
|
|
int ret;
|
|
|
|
/* allow to change the current function only during debugging */
|
|
if (g->is_instep && g->core->io->debug) {
|
|
RAnalFunction *f;
|
|
r_core_cmd0 (g->core, "sr pc");
|
|
f = r_anal_get_fcn_in (g->core->anal, g->core->offset, 0);
|
|
if (f && f != g->fcn) {
|
|
g->fcn = f;
|
|
g->need_reload_nodes = R_TRUE;
|
|
}
|
|
}
|
|
|
|
/* look for any change in the state of the graph
|
|
* and update what's necessary */
|
|
if (g->need_reload_nodes) {
|
|
ret = agraph_reload_nodes(g);
|
|
if (!ret)
|
|
return R_FALSE;
|
|
|
|
g->need_reload_nodes = R_FALSE;
|
|
}
|
|
if (g->need_update_dim) {
|
|
update_node_dimension (g->graph, g->is_small_nodes, g->zoom);
|
|
g->need_update_dim = R_FALSE;
|
|
}
|
|
if (g->need_set_layout) {
|
|
agraph_set_layout (g);
|
|
g->need_set_layout = R_FALSE;
|
|
}
|
|
if (g->update_seek_on) {
|
|
update_seek(g->can, g->update_seek_on, g->force_update_seek);
|
|
g->update_seek_on = NULL;
|
|
g->force_update_seek = R_FALSE;
|
|
}
|
|
|
|
if (fs) {
|
|
r_cons_clear00 ();
|
|
}
|
|
|
|
h = fs ? h : 1024;
|
|
r_cons_canvas_resize (g->can, w, h);
|
|
r_cons_canvas_clear (g->can);
|
|
|
|
agraph_print_edges(g);
|
|
agraph_print_nodes(g);
|
|
|
|
if (fs) {
|
|
(void)G (-g->can->sx, -g->can->sy);
|
|
snprintf (title, sizeof (title)-1,
|
|
"[0x%08"PFMT64x"]> %d VV @ %s (nodes %d edges %d zoom %d%%) %s mouse:%s movements-speed:%d",
|
|
g->fcn->addr, r_stack_size (g->history), g->fcn->name,
|
|
g->graph->n_nodes, g->graph->n_edges, g->zoom, g->is_callgraph?"CG":"BB",
|
|
mousemodes[mousemode], g->movspeed);
|
|
W (title);
|
|
}
|
|
|
|
if (fs) {
|
|
r_cons_canvas_print (g->can);
|
|
} else {
|
|
r_cons_canvas_print_region (g->can);
|
|
}
|
|
if (fs) {
|
|
const char *cmdv = r_config_get (g->core->config, "cmd.gprompt");
|
|
if (cmdv && *cmdv) {
|
|
r_cons_gotoxy (0, 1);
|
|
r_core_cmd0 (g->core, cmdv);
|
|
}
|
|
}
|
|
r_cons_flush_nonewline ();
|
|
return R_TRUE;
|
|
}
|
|
|
|
static void agraph_toggle_speed (AGraph *g) {
|
|
int alt = r_config_get_i (g->core->config, "graph.scroll");
|
|
|
|
g->movspeed = g->movspeed == DEFAULT_SPEED ? alt : DEFAULT_SPEED;
|
|
}
|
|
|
|
static void agraph_init(AGraph *g) {
|
|
g->is_callgraph = R_FALSE;
|
|
g->is_instep = R_FALSE;
|
|
g->is_simple_mode = R_TRUE;
|
|
g->is_small_nodes = R_FALSE;
|
|
g->need_reload_nodes = R_TRUE;
|
|
g->force_update_seek = R_TRUE;
|
|
g->history = r_stack_new (INIT_HISTORY_CAPACITY);
|
|
g->graph = r_graph_new ();
|
|
g->zoom = ZOOM_DEFAULT;
|
|
g->movspeed = DEFAULT_SPEED;
|
|
}
|
|
|
|
static AGraph *agraph_new(RCore *core, RConsCanvas *can, RAnalFunction *fcn) {
|
|
AGraph *g = R_NEW0 (AGraph);
|
|
if (!g) return NULL;
|
|
|
|
g->core = core;
|
|
g->can = can;
|
|
g->fcn = fcn;
|
|
|
|
agraph_init(g);
|
|
return g;
|
|
}
|
|
|
|
R_API int r_core_visual_graph(RCore *core, RAnalFunction *_fcn, int is_interactive) {
|
|
int exit_graph = R_FALSE, is_error = R_FALSE;
|
|
struct agraph_refresh_data *grd;
|
|
int okey, key, wheel;
|
|
RAnalFunction *fcn;
|
|
const char *key_s;
|
|
RConsCanvas *can;
|
|
AGraph *g;
|
|
int wheelspeed;
|
|
int w, h;
|
|
int ret;
|
|
|
|
fcn = _fcn? _fcn: r_anal_get_fcn_in (core->anal, core->offset, 0);
|
|
if (!fcn) {
|
|
eprintf ("No function in current seek\n");
|
|
return R_FALSE;
|
|
}
|
|
w = r_cons_get_size (&h);
|
|
can = r_cons_canvas_new (w, h);
|
|
if (!can) {
|
|
eprintf ("Cannot create RCons.canvas context\n");
|
|
return R_FALSE;
|
|
}
|
|
can->linemode = 1;
|
|
can->color = r_config_get_i (core->config, "scr.color");
|
|
// disable colors in disasm because canvas doesnt supports ansi text yet
|
|
r_config_set_i (core->config, "scr.color", 0);
|
|
|
|
g = agraph_new (core, can, fcn);
|
|
if (!g) {
|
|
is_error = R_TRUE;
|
|
goto err_graph_new;
|
|
}
|
|
|
|
grd = R_NEW (struct agraph_refresh_data);
|
|
grd->g = g;
|
|
grd->fs = is_interactive;
|
|
|
|
core->cons->event_data = grd;
|
|
core->cons->event_resize = (RConsEvent)agraph_refresh;
|
|
|
|
while (!exit_graph && !is_error) {
|
|
w = r_cons_get_size (&h);
|
|
ret = agraph_refresh (grd);
|
|
if (!ret) {
|
|
is_error = R_TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!is_interactive) {
|
|
/* this is a non-interactive ascii-art graph, so exit the loop */
|
|
r_cons_printf (Color_RESET);
|
|
break;
|
|
}
|
|
|
|
r_cons_show_cursor(R_FALSE);
|
|
wheel = r_config_get_i (core->config, "scr.wheel");
|
|
if (wheel)
|
|
r_cons_enable_mouse (R_TRUE);
|
|
|
|
// r_core_graph_inputhandle()
|
|
okey = r_cons_readchar ();
|
|
key = r_cons_arrow_to_hjkl (okey);
|
|
wheelspeed = r_config_get_i (core->config, "scr.wheelspeed");
|
|
|
|
switch (key) {
|
|
case '-':
|
|
agraph_set_zoom (g, g->zoom - ZOOM_STEP);
|
|
break;
|
|
case '+':
|
|
agraph_set_zoom (g, g->zoom + ZOOM_STEP);
|
|
break;
|
|
case '0':
|
|
agraph_set_zoom (g, ZOOM_DEFAULT);
|
|
agraph_update_seek (g, get_anode (g->curnode), R_TRUE);
|
|
break;
|
|
case '|':
|
|
{ // TODO: edit
|
|
const char *buf = NULL;
|
|
const char *cmd = r_config_get (core->config, "cmd.gprompt");
|
|
r_line_set_prompt ("cmd.gprompt> ");
|
|
core->cons->line->contents = strdup (cmd);
|
|
buf = r_line_readline ();
|
|
core->cons->line->contents = NULL;
|
|
r_config_set (core->config, "cmd.gprompt", buf);
|
|
}
|
|
break;
|
|
case 'O':
|
|
agraph_toggle_simple_mode(g);
|
|
break;
|
|
case 'V':
|
|
agraph_toggle_callgraph(g);
|
|
break;
|
|
case 'z':
|
|
g->is_instep = R_TRUE;
|
|
key_s = r_config_get (core->config, "key.s");
|
|
if (key_s && *key_s) {
|
|
r_core_cmd0 (core, key_s);
|
|
} else {
|
|
if (r_config_get_i (core->config, "cfg.debug"))
|
|
r_core_cmd0 (core, "ds;.dr*");
|
|
else
|
|
r_core_cmd0 (core, "aes;.dr*");
|
|
}
|
|
ret = agraph_reload_nodes(g);
|
|
if (!ret)
|
|
is_error = R_TRUE;
|
|
break;
|
|
case 'Z':
|
|
if (okey == 27) {
|
|
agraph_prev_node(g);
|
|
} else {
|
|
// 'Z'
|
|
g->is_instep = R_TRUE;
|
|
if (r_config_get_i (core->config, "cfg.debug"))
|
|
r_core_cmd0 (core, "dso;.dr*");
|
|
else
|
|
r_core_cmd0 (core, "aeso;.dr*");
|
|
|
|
ret = agraph_reload_nodes(g);
|
|
if (!ret)
|
|
is_error = R_TRUE;
|
|
}
|
|
break;
|
|
case 'x':
|
|
if (r_core_visual_xrefs_x (core))
|
|
exit_graph = R_TRUE;
|
|
break;
|
|
case 'X':
|
|
if (r_core_visual_xrefs_X (core))
|
|
exit_graph = R_TRUE;
|
|
break;
|
|
case 9: // tab
|
|
agraph_next_node(g);
|
|
break;
|
|
case '?':
|
|
r_cons_clear00 ();
|
|
r_cons_printf ("Visual Ascii Art graph keybindings:\n"
|
|
" . - center graph to the current node\n"
|
|
" C - toggle scr.color\n"
|
|
" hjkl - move node\n"
|
|
" HJKL - scroll canvas\n"
|
|
" tab - select next node\n"
|
|
" TAB - select previous node\n"
|
|
" t/f - follow true/false edges\n"
|
|
" e - toggle edge-lines style (diagonal/square)\n"
|
|
" O - toggle disasm mode\n"
|
|
" p - toggle mini-graph\n"
|
|
" u - select previous node\n"
|
|
" V - toggle basicblock / call graphs\n"
|
|
" w - toggle between movements speed 1 and graph.scroll\n"
|
|
" x/X - jump to xref/ref\n"
|
|
" z/Z - step / step over\n"
|
|
" +/-/0 - zoom in/out/default\n"
|
|
" R - relayout\n");
|
|
r_cons_flush ();
|
|
r_cons_any_key (NULL);
|
|
break;
|
|
case 'R':
|
|
case 'r':
|
|
agraph_set_layout (g);
|
|
break;
|
|
case 'j':
|
|
if (r_cons_singleton()->mouse_event) {
|
|
switch (mousemode) {
|
|
case 0: // canvas-y
|
|
can->sy += wheelspeed;
|
|
break;
|
|
case 1: // canvas-x
|
|
can->sx += wheelspeed;
|
|
break;
|
|
case 2: // node-y
|
|
get_anode(g->curnode)->y += wheelspeed;
|
|
break;
|
|
case 3: // node-x
|
|
get_anode(g->curnode)->x += wheelspeed;
|
|
break;
|
|
}
|
|
} else {
|
|
get_anode(g->curnode)->y += g->movspeed;
|
|
}
|
|
break;
|
|
case 'k':
|
|
if (r_cons_singleton()->mouse_event) {
|
|
switch (mousemode) {
|
|
case 0: // canvas-y
|
|
can->sy -= wheelspeed;
|
|
break;
|
|
case 1: // canvas-x
|
|
can->sx -= wheelspeed;
|
|
break;
|
|
case 2: // node-y
|
|
get_anode(g->curnode)->y -= wheelspeed;
|
|
break;
|
|
case 3: // node-x
|
|
get_anode(g->curnode)->x -= wheelspeed;
|
|
break;
|
|
}
|
|
} else {
|
|
get_anode(g->curnode)->y -= g->movspeed;
|
|
}
|
|
break;
|
|
case 'm':
|
|
mousemode++;
|
|
if (!mousemodes[mousemode])
|
|
mousemode = 0;
|
|
break;
|
|
case 'M':
|
|
mousemode--;
|
|
if (mousemode<0)
|
|
mousemode = 3;
|
|
break;
|
|
case 'h': get_anode(g->curnode)->x -= g->movspeed; break;
|
|
case 'l': get_anode(g->curnode)->x += g->movspeed; break;
|
|
|
|
case 'K': can->sy -= g->movspeed; break;
|
|
case 'J': can->sy += g->movspeed; break;
|
|
case 'H': can->sx -= g->movspeed; break;
|
|
case 'L': can->sx += g->movspeed; break;
|
|
case 'e':
|
|
can->linemode = !!!can->linemode;
|
|
break;
|
|
case 'p':
|
|
agraph_toggle_small_nodes (g);
|
|
agraph_update_seek (g, get_anode (g->curnode), R_TRUE);
|
|
break;
|
|
case 'u':
|
|
agraph_undo_node(g);
|
|
break;
|
|
case '.':
|
|
agraph_update_seek (g, get_anode (g->curnode), R_TRUE);
|
|
g->is_instep = R_TRUE;
|
|
break;
|
|
case 't':
|
|
agraph_follow_true (g);
|
|
break;
|
|
case 'f':
|
|
agraph_follow_false (g);
|
|
break;
|
|
case '/':
|
|
r_core_cmd0 (core, "?i highlight;e scr.highlight=`?y`");
|
|
break;
|
|
case ':':
|
|
core->vmode = R_FALSE;
|
|
r_core_visual_prompt_input (core);
|
|
core->vmode = R_TRUE;
|
|
break;
|
|
case 'C':
|
|
can->color = !!!can->color;
|
|
//r_config_swap (core->config, "scr.color");
|
|
// refresh graph
|
|
break;
|
|
case 'w':
|
|
agraph_toggle_speed (g);
|
|
break;
|
|
case -1: // EOF
|
|
case 'q':
|
|
exit_graph = R_TRUE;
|
|
break;
|
|
case 27: // ESC
|
|
if (r_cons_readchar () == 91) {
|
|
if (r_cons_readchar () == 90) {
|
|
agraph_prev_node (g);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
free (grd);
|
|
agraph_free(g);
|
|
err_graph_new:
|
|
r_config_set_i (core->config, "scr.color", can->color);
|
|
free (can);
|
|
return !is_error;
|
|
}
|