Graphviz 13.0.0~dev.20250121.0651
Loading...
Searching...
No Matches
node.c
Go to the documentation of this file.
1
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * https://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: Details at https://graphviz.org
12 *************************************************************************/
13
14#include <assert.h>
15#include <cgraph/cghdr.h>
16#include <cgraph/node_set.h>
17#include <stdbool.h>
18#include <stdlib.h>
19#include <util/alloc.h>
20#include <util/unreachable.h>
21
23{
24 Agsubnode_t *sn;
25
26 sn = node_set_find(g->n_id, id);
27 return sn ? sn->node : NULL;
28}
29
30static Agnode_t *agfindnode_by_name(Agraph_t * g, char *name)
31{
32 IDTYPE id;
33
34 if (agmapnametoid(g, AGNODE, name, &id, false))
35 return agfindnode_by_id(g, id);
36 else
37 return NULL;
38}
39
41{
42 Agsubnode_t *sn;
43 sn = dtfirst(g->n_seq);
44 return sn ? sn->node : NULL;
45}
46
48{
49 Agsubnode_t *sn;
50 sn = agsubrep(g, n);
51 if (sn) sn = dtnext(g->n_seq, sn);
52 return sn ? sn->node : NULL;
53}
54
56{
57 Agsubnode_t *sn;
58 sn = dtlast(g->n_seq);
59 return sn ? sn->node : NULL;
60}
61
63{
64 Agsubnode_t *sn;
65 sn = agsubrep(g, n);
66 if (sn) sn = dtprev(g->n_seq, sn);
67 return sn ? sn->node : NULL;
68}
69
70
71/* internal node constructor */
72static Agnode_t *newnode(Agraph_t * g, IDTYPE id, uint64_t seq)
73{
74 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
75
76 Agnode_t *n = gv_alloc(sizeof(Agnode_t));
77 AGTYPE(n) = AGNODE;
78 AGID(n) = id;
79 AGSEQ(n) = seq & SEQ_MASK;
80 n->root = agroot(g);
81 if (agroot(g)->desc.has_attrs)
82 (void)agbindrec(n, AgDataRecName, sizeof(Agattr_t), false);
83 /* nodeattr_init and method_init will be called later, from the
84 * subgraph where the node was actually created, but first it has
85 * to be installed in all the (sub)graphs up to root. */
86 return n;
87}
88
89static void installnode(Agraph_t * g, Agnode_t * n)
90{
91 Agsubnode_t *sn;
92 size_t osize;
93 (void)osize;
94
95 assert(node_set_size(g->n_id) == (size_t)dtsize(g->n_seq));
96 osize = node_set_size(g->n_id);
97 if (g == agroot(g)) sn = &(n->mainsub);
98 else sn = gv_alloc(sizeof(Agsubnode_t));
99 sn->node = n;
100 node_set_add(g->n_id, sn);
101 dtinsert(g->n_seq, sn);
102 assert(node_set_size(g->n_id) == (size_t)dtsize(g->n_seq));
103 assert(node_set_size(g->n_id) == osize + 1);
104}
105
107{
108 Agraph_t *par;
109 installnode(g, n);
110 if ((par = agparent(g)))
111 installnodetoroot(par, n);
112}
113
114static void initnode(Agraph_t * g, Agnode_t * n)
115{
116 if (agroot(g)->desc.has_attrs)
117 agnodeattr_init(g,n);
118 agmethod_init(g, n);
119}
120
121/* external node constructor - create by id */
122Agnode_t *agidnode(Agraph_t * g, IDTYPE id, int cflag)
123{
124 Agraph_t *root;
125 Agnode_t *n;
126
127 n = agfindnode_by_id(g, id);
128 if (n == NULL && cflag) {
129 root = agroot(g);
130 if (g != root && (n = agfindnode_by_id(root, id))) // old
131 agsubnode(g, n, 1); /* insert locally */
132 else {
133 n = NULL;
134 }
135 }
136 /* else return probe result */
137 return n;
138}
139
140Agnode_t *agnode(Agraph_t * g, char *name, int cflag)
141{
142 Agraph_t *root;
143 Agnode_t *n;
144 IDTYPE id;
145
146 root = agroot(g);
147 /* probe for existing node */
148 if (agmapnametoid(g, AGNODE, name, &id, false)) {
149 if ((n = agfindnode_by_id(g, id)))
150 return n;
151
152 /* might already exist globally, but need to insert locally */
153 if (cflag && g != root && (n = agfindnode_by_id(root, id))) {
154 return agsubnode(g, n, 1);
155 }
156 }
157
158 if (cflag && agmapnametoid(g, AGNODE, name, &id, true)) { /* reserve id */
159 n = newnode(g, id, agnextseq(g, AGNODE));
160 installnodetoroot(g, n);
161 initnode(g, n);
162 assert(agsubrep(g,n));
163 agregister(g, AGNODE, n); /* register in external namespace */
164 return n;
165 }
166
167 return NULL;
168}
169
170/* removes image of node and its edges from graph.
171 caller must ensure n belongs to g. */
172void agdelnodeimage(Agraph_t * g, Agnode_t * n, void *ignored)
173{
174 Agedge_t *e, *f;
175 Agsubnode_t template = {0};
176 template.node = n;
177
178 (void)ignored;
179 for (e = agfstedge(g, n); e; e = f) {
180 f = agnxtedge(g, e, n);
181 agdeledgeimage(g, e, 0);
182 }
183 /* If the following lines are switched, switch the discpline using
184 * free_subnode below.
185 */
187 dtdelete(g->n_seq, &template);
188}
189
191{
192 Agedge_t *e, *f;
193
194 if (!agfindnode_by_id(g, AGID(n)))
195 return FAILURE; /* bad arg */
196 if (g == agroot(g)) {
197 for (e = agfstedge(g, n); e; e = f) {
198 f = agnxtedge(g, e, n);
199 agdeledge(g, e);
200 }
201 if (g->desc.has_attrs)
203 agmethod_delete(g, n);
204 agrecclose((Agobj_t *) n);
205 agfreeid(g, AGNODE, AGID(n));
206 }
207 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agdelnodeimage, NULL, false) == SUCCESS) {
208 if (g == agroot(g))
209 free(n);
210 return SUCCESS;
211 } else
212 return FAILURE;
213}
214
215static void dict_relabel(Agraph_t *ignored, Agnode_t *n, void *arg) {
216 (void)ignored;
217
218 Agraph_t *g;
219 uint64_t new_id;
220
221 g = agraphof(n);
222 new_id = *(uint64_t *) arg;
223 Agsubnode_t *key = agsubrep(g, n);
224 assert(key != NULL && "node being renamed does not exist");
225 node_set_remove(g->n_id, key->node->base.tag.id);
226 AGID(key->node) = new_id;
227 node_set_add(g->n_id, key);
228 /* because all the subgraphs share the same node now, this
229 now requires a separate deletion and insertion phase */
230}
231
232int agrelabel_node(Agnode_t * n, char *newname)
233{
234 Agraph_t *g;
235 IDTYPE new_id;
236
237 g = agroot(agraphof(n));
238 if (agfindnode_by_name(g, newname))
239 return FAILURE;
240 if (agmapnametoid(g, AGNODE, newname, &new_id, true)) {
241 if (agfindnode_by_id(agroot(g), new_id) == NULL) {
242 agfreeid(g, AGNODE, AGID(n));
243 agapply(g, (Agobj_t*)n, (agobjfn_t)dict_relabel, &new_id, false);
244 return SUCCESS;
245 } else {
246 agfreeid(g, AGNODE, new_id); /* couldn't use it after all */
247 }
248 /* obj* is unchanged, so no need to re agregister() */
249 }
250 return FAILURE;
251}
252
253/* lookup or insert <n> in <g> */
254Agnode_t *agsubnode(Agraph_t * g, Agnode_t * n0, int cflag)
255{
256 Agraph_t *par;
257 Agnode_t *n;
258
259 if (g->root != n0->root)
260 return NULL;
261 n = agfindnode_by_id(g, AGID(n0));
262 if (n == NULL && cflag) {
263 if ((par = agparent(g))) {
264 n = agsubnode(par, n0, cflag);
265 installnode(g, n);
266 /* no callback for existing node insertion in subgraph (?) */
267 }
268 /* else impossible that <n> doesn't belong to <g> */
269 }
270 /* else lookup succeeded */
271 return n;
272}
273
279static bool agsubnodeideq(const Agsubnode_t *sn0, IDTYPE id) {
280 return AGID(sn0->node) == id;
281}
282
283static int agsubnodeseqcmpf(void *arg0, void *arg1) {
284 Agsubnode_t *sn0 = arg0;
285 Agsubnode_t *sn1 = arg1;
286
287 if (AGSEQ(sn0->node) < AGSEQ(sn1->node)) return -1;
288 if (AGSEQ(sn0->node) > AGSEQ(sn1->node)) return 1;
289 return 0;
290}
291
292/* free_subnode:
293 * Free Agsubnode_t allocated in installnode. This should
294 * only be done for subgraphs, as the root graph uses the
295 * subnode structure built into the node. This explains the
296 * AGSNMAIN test. Also, note that both the id and the seq
297 * dictionaries use the same subnode object, so only one
298 * should do the deletion.
299 */
300static void free_subnode(void *subnode) {
301 Agsubnode_t *sn = subnode;
302 if (!AGSNMAIN(sn))
303 free(sn);
304}
305
307 .link = offsetof(Agsubnode_t, seq_link), // link offset
308 .freef = free_subnode,
309 .comparf = agsubnodeseqcmpf,
310};
311
312static void agnodesetfinger(Agraph_t * g, Agnode_t * n, void *ignored)
313{
314 Agsubnode_t template = {0};
315 template.node = n;
316 dtsearch(g->n_seq,&template);
317 (void)ignored;
318}
319
320static void agnoderenew(Agraph_t * g, Agnode_t * n, void *ignored)
321{
322 dtrenew(g->n_seq, dtfinger(g->n_seq));
323 (void)n;
324 (void)ignored;
325}
326
328{
329 Agraph_t *g;
330 Agnode_t *n, *nxt;
331
332
333 g = agroot(fst);
334 if (AGSEQ(fst) > AGSEQ(snd)) return SUCCESS;
335
336 /* move snd out of the way somewhere */
337 n = snd;
338 if (agapply (g,(Agobj_t *)n, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
339 return FAILURE;
340 }
341 {
342 uint64_t seq = g->clos->seq[AGNODE] + 2;
343 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
344 AGSEQ(snd) = seq & SEQ_MASK;
345 }
346 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnoderenew, n, false) != SUCCESS) {
347 return FAILURE;
348 }
349 n = agprvnode(g,snd);
350 do {
351 nxt = agprvnode(g,n);
352 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
353 return FAILURE;
354 }
355 uint64_t seq = AGSEQ(n) + 1;
356 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
357 AGSEQ(n) = seq & SEQ_MASK;
358 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnoderenew, n, false) != SUCCESS) {
359 return FAILURE;
360 }
361 if (n == fst) break;
362 n = nxt;
363 } while (n);
364 if (agapply(g, (Agobj_t *)snd, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
365 return FAILURE;
366 }
367 assert(AGSEQ(fst) != 0 && "sequence ID overflow");
368 AGSEQ(snd) = (AGSEQ(fst) - 1) & SEQ_MASK;
369 if (agapply(g, (Agobj_t *)snd, (agobjfn_t)agnoderenew, snd, false) != SUCCESS) {
370 return FAILURE;
371 }
372 return SUCCESS;
373}
374
391
393static Agsubnode_t *const TOMBSTONE = (Agsubnode_t *)-1;
394
403static size_t node_set_get_capacity(const node_set_t *self) {
404 assert(self != NULL);
405 return self->slots == NULL ? 0 : 1ul << self->capacity_exp;
406}
407
408node_set_t *node_set_new(void) { return gv_alloc(sizeof(node_set_t)); }
409
418static size_t node_set_hash(IDTYPE id) { return (size_t)id; }
419
421 assert(self != NULL);
422 assert(item != NULL);
423
424 // a watermark ratio at which the set capacity should be expanded
425 static const size_t OCCUPANCY_THRESHOLD_PERCENT = 70;
426
427 // do we need to expand the backing store?
428 size_t capacity = node_set_get_capacity(self);
429 const bool grow = 100 * self->size >= OCCUPANCY_THRESHOLD_PERCENT * capacity;
430
431 if (grow) {
432 const size_t new_c = capacity == 0 ? 10 : self->capacity_exp + 1;
433 Agsubnode_t **new_slots = gv_calloc(1ul << new_c, sizeof(Agsubnode_t *));
434
435 // Construct a new set and copy everything into it. Note we need to rehash
436 // because capacity (and hence modulo wraparound behavior) has changed. This
437 // conveniently flushes out the tombstones too.
438 node_set_t new_self = {.slots = new_slots, .capacity_exp = new_c};
439 for (size_t i = 0; i < capacity; ++i) {
440 // skip empty slots
441 if (self->slots[i] == NULL) {
442 continue;
443 }
444 // skip deleted slots
445 if (self->slots[i] == TOMBSTONE) {
446 continue;
447 }
448 node_set_add(&new_self, self->slots[i]);
449 }
450
451 // replace ourselves with this new set
452 free(self->slots);
453 *self = new_self;
454 }
455
456 // update bounds of what we have seen
457 if (!self->min_initialized || item->node->base.tag.id < self->min) {
458 self->min_initialized = true;
459 self->min = item->node->base.tag.id;
460 }
461 if (item->node->base.tag.id > self->max) {
462 self->max = item->node->base.tag.id;
463 }
464
465 capacity = node_set_get_capacity(self);
466 assert(capacity > self->size);
467
468 const size_t hash = node_set_hash(item->node->base.tag.id);
469
470 for (size_t i = 0; i < capacity; ++i) {
471 const size_t candidate = (hash + i) % capacity;
472
473 // if we found an empty slot or a previously deleted slot, we can insert
474 if (self->slots[candidate] == NULL || self->slots[candidate] == TOMBSTONE) {
475 self->slots[candidate] = item;
476 ++self->size;
477 return;
478 }
479 }
480
481 UNREACHABLE();
482}
483
485 assert(self != NULL);
486
487 // do we know immediately a node of this key has never been inserted?
488 if (self->min_initialized && key < self->min) {
489 return NULL;
490 }
491 if (key > self->max) {
492 return NULL;
493 }
494
495 const size_t hash = node_set_hash(key);
496 const size_t capacity = node_set_get_capacity(self);
497
498 for (size_t i = 0; i < capacity; ++i) {
499 const size_t candidate = (hash + i) % capacity;
500
501 // if we found an empty slot, the sought item does not exist
502 if (self->slots[candidate] == NULL) {
503 return NULL;
504 }
505
506 // if we found a previously deleted slot, skip over it
507 if (self->slots[candidate] == TOMBSTONE) {
508 continue;
509 }
510
511 if (agsubnodeideq(self->slots[candidate], key)) {
512 return self->slots[candidate];
513 }
514 }
515
516 return NULL;
517}
518
520 assert(self != NULL);
521
522 const size_t hash = node_set_hash(item);
523 const size_t capacity = node_set_get_capacity(self);
524
525 for (size_t i = 0; i < capacity; ++i) {
526 const size_t candidate = (hash + i) % capacity;
527
528 // if we found an empty slot, the sought item does not exist
529 if (self->slots[candidate] == NULL) {
530 return;
531 }
532
533 // if we found a previously deleted slot, skip over it
534 if (self->slots[candidate] == TOMBSTONE) {
535 continue;
536 }
537
538 if (agsubnodeideq(self->slots[candidate], item)) {
539 assert(self->size > 0);
540 self->slots[candidate] = TOMBSTONE;
541 --self->size;
542 return;
543 }
544 }
545}
546
547size_t node_set_size(const node_set_t *self) {
548 assert(self != NULL);
549 return self->size;
550}
551
553 assert(self != NULL);
554
555 if (*self != NULL) {
556 free((*self)->slots);
557 }
558
559 free(*self);
560 *self = NULL;
561}
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
int agapply(Agraph_t *g, Agobj_t *obj, agobjfn_t fn, void *arg, int preorder)
Definition apply.c:60
char * AgDataRecName
Definition attr.c:173
#define dtsearch(d, o)
Definition cdt.h:183
CDT_API int dtsize(Dt_t *)
Definition dtsize.c:12
#define dtfinger(d)
Definition cdt.h:177
#define dtinsert(d, o)
Definition cdt.h:185
#define dtprev(d, o)
Definition cdt.h:182
CDT_API void * dtrenew(Dt_t *, void *)
Definition dtrenew.c:9
#define dtlast(d)
Definition cdt.h:181
#define dtdelete(d, o)
Definition cdt.h:186
#define dtnext(d, o)
Definition cdt.h:180
#define dtfirst(d)
Definition cdt.h:179
cgraph.h additions
void agfreeid(Agraph_t *g, int objtype, IDTYPE id)
Definition id.c:131
void agrecclose(Agobj_t *obj)
Definition rec.c:227
int agmapnametoid(Agraph_t *g, int objtype, char *str, IDTYPE *result, bool createflag)
Definition id.c:102
void agdeledgeimage(Agraph_t *g, Agedge_t *edge, void *ignored)
Definition edge.c:300
uint64_t agnextseq(Agraph_t *g, int objtype)
Definition graph.c:160
#define FAILURE
Definition cghdr.h:45
void agregister(Agraph_t *g, int objtype, void *obj)
Definition id.c:170
#define SUCCESS
Definition cghdr.h:44
@ SEQ_MASK
Definition cghdr.h:74
static Agnode_t * agfindnode_by_name(Agraph_t *g, char *name)
Definition node.c:30
static Agsubnode_t *const TOMBSTONE
a sentinel, marking a set slot from which an element has been deleted
Definition node.c:393
static int agsubnodeseqcmpf(void *arg0, void *arg1)
Definition node.c:283
Agsubnode_t * node_set_find(node_set_t *self, IDTYPE key)
Definition node.c:484
static size_t node_set_hash(IDTYPE id)
Definition node.c:418
Agnode_t * agfindnode_by_id(Agraph_t *g, IDTYPE id)
Definition node.c:22
void agdelnodeimage(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:172
Dtdisc_t Ag_subnode_seq_disc
Definition node.c:306
static void installnode(Agraph_t *g, Agnode_t *n)
Definition node.c:89
static void installnodetoroot(Agraph_t *g, Agnode_t *n)
Definition node.c:106
static void initnode(Agraph_t *g, Agnode_t *n)
Definition node.c:114
static void dict_relabel(Agraph_t *ignored, Agnode_t *n, void *arg)
Definition node.c:215
void node_set_free(node_set_t **self)
Definition node.c:552
static void agnoderenew(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:320
size_t node_set_size(const node_set_t *self)
Definition node.c:547
static bool agsubnodeideq(const Agsubnode_t *sn0, IDTYPE id)
Definition node.c:279
static Agnode_t * newnode(Agraph_t *g, IDTYPE id, uint64_t seq)
Definition node.c:72
node_set_t * node_set_new(void)
Definition node.c:408
void node_set_add(node_set_t *self, Agsubnode_t *item)
Definition node.c:420
void node_set_remove(node_set_t *self, IDTYPE item)
Definition node.c:519
static size_t node_set_get_capacity(const node_set_t *self)
Definition node.c:403
static void free_subnode(void *subnode)
Definition node.c:300
static void agnodesetfinger(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:312
#define hash
Definition dthdr.h:13
void free(void *)
node NULL
Definition grammar.y:163
void agnodeattr_init(Agraph_t *g, Agnode_t *n)
Definition attr.c:427
void agnodeattr_delete(Agnode_t *n)
Definition attr.c:436
void agmethod_delete(Agraph_t *g, void *obj)
Definition obj.c:138
void agmethod_init(Agraph_t *g, void *obj)
Definition obj.c:78
int agdeledge(Agraph_t *g, Agedge_t *arg_e)
Definition edge.c:330
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:94
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:85
#define AGSNMAIN(sn)
Definition cgraph.h:916
void(* agobjfn_t)(Agraph_t *g, Agobj_t *obj, void *arg)
Definition cgraph.h:368
Agnode_t * agnode(Agraph_t *g, char *name, int cflag)
Definition node.c:140
int agnodebefore(Agnode_t *fst, Agnode_t *snd)
Definition node.c:327
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agprvnode(Agraph_t *g, Agnode_t *n)
Definition node.c:62
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
Agnode_t * agsubnode(Agraph_t *g, Agnode_t *n0, int cflag)
Definition node.c:254
Agnode_t * agidnode(Agraph_t *g, IDTYPE id, int cflag)
Definition node.c:122
Agnode_t * aglstnode(Agraph_t *g)
Definition node.c:55
int agrelabel_node(Agnode_t *n, char *newname)
Definition node.c:232
int agdelnode(Agraph_t *g, Agnode_t *n)
removes a node from a graph or subgraph.
Definition node.c:190
Agsubnode_t * agsubrep(Agraph_t *g, Agnode_t *n)
Definition edge.c:145
Agraph_t * agraphof(void *obj)
Definition obj.c:185
#define AGID(obj)
returns the unique integer ID associated with the object
Definition cgraph.h:221
uint64_t IDTYPE
unique per main graph ID
Definition cgraph.h:73
#define AGTYPE(obj)
returns AGRAPH, AGNODE, or AGEDGE depending on the type of the object
Definition cgraph.h:216
Agraph_t * agroot(void *obj)
Definition obj.c:168
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGNODE
Definition cgraph.h:207
void * agbindrec(void *obj, const char *name, unsigned int recsize, int move_to_front)
attaches a new record of the given size to the object
Definition rec.c:89
Agraph_t * agparent(Agraph_t *g)
Definition subg.c:88
static uint64_t id
Definition gv2gml.c:42
unordered set of Agsubnode_t *
string attribute container
Definition cgraph.h:633
uint64_t seq[3]
Definition cgraph.h:414
unsigned has_attrs
Definition cgraph.h:290
Agsubnode_t mainsub
Definition cgraph.h:262
Agraph_t * root
Definition cgraph.h:261
Agobj_t base
Definition cgraph.h:260
a generic header of Agraph_s, Agnode_s and Agedge_s
Definition cgraph.h:210
Agtag_t tag
access with AGTAG
Definition cgraph.h:211
graph or subgraph
Definition cgraph.h:424
struct graphviz_node_set * n_id
the node set indexed by ID
Definition cgraph.h:430
Agraph_t * root
subgraphs - ancestors
Definition cgraph.h:438
Dict_t * n_seq
the node set in sequence
Definition cgraph.h:429
Agclos_t * clos
shared resources
Definition cgraph.h:439
Agdesc_t desc
Definition cgraph.h:426
This is the node struct allocated per graph (or subgraph).
Definition cgraph.h:251
Agnode_t * node
Definition cgraph.h:254
IDTYPE id
Definition cgraph.h:203
int link
Definition cdt.h:87
bool min_initialized
Definition node.c:387
size_t capacity_exp
logâ‚‚ size of slots
Definition node.c:378
size_t size
number of elements in the set
Definition node.c:377
Agsubnode_t ** slots
backing store for elements
Definition node.c:376
Definition utils.c:749
#define UNREACHABLE()
Definition unreachable.h:30