ACPI AML decompiler w/ CFG recovery and structured pseudocode
29

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #8 from 23384/dev

Expand graph visualisation coverage w semantic namespace relationships/corpus validation

+501 -33
+255 -26
src/cf.c
··· 113 113 dot_escape_limited(sb, s, 28, 96); 114 114 } 115 115 116 + static void dot_label_text_full(tobi_sb *sb, const char *s) { 117 + dot_escape_limited(sb, s, 44, 0); 118 + } 119 + 116 120 typedef struct { 117 121 size_t stmt_next; 118 122 size_t edge_next; 123 + tobi_graph_labels labels; 119 124 } dot_ctx; 120 125 121 126 static size_t dot_stmt(tobi_sb *sb, const tobi_ir *n, dot_ctx *ctx); ··· 154 159 return emitted; 155 160 } 156 161 157 - static void dot_ir_label(tobi_sb *sb, const tobi_ir *n) { 162 + static void dot_label_value(tobi_sb *sb, const char *s, tobi_graph_labels labels) { 163 + if (labels == TOBI_GRAPH_LABEL_FULL) { 164 + dot_label_text_full(sb, s); 165 + } else { 166 + dot_label_text(sb, s); 167 + } 168 + } 169 + 170 + static void dot_ir_label(tobi_sb *sb, const tobi_ir *n, tobi_graph_labels labels) { 171 + if (labels == TOBI_GRAPH_LABEL_NONE) { 172 + return; 173 + } 158 174 tobi_sb_add(sb, tobi_ir_kind_name(n->kind)); 159 175 if (n->name && n->name[0]) { 160 176 tobi_sb_add(sb, "\\n"); 161 - dot_label_text(sb, n->name); 177 + dot_label_value(sb, n->name, labels); 162 178 } else if (n->path && n->path[0]) { 163 179 tobi_sb_add(sb, "\\n"); 164 - dot_label_text(sb, n->path); 180 + dot_label_value(sb, n->path, labels); 165 181 } else if (n->str && n->str[0]) { 166 182 tobi_sb_add(sb, "\\n"); 167 - dot_label_text(sb, n->str); 183 + dot_label_value(sb, n->str, labels); 184 + } 185 + if (labels == TOBI_GRAPH_LABEL_FULL) { 186 + if (n->path && n->path[0] && (!n->name || strcmp(n->name, n->path) != 0)) { 187 + tobi_sb_add(sb, "\\npath="); 188 + dot_label_value(sb, n->path, labels); 189 + } 190 + if (n->scope && n->scope[0]) { 191 + tobi_sb_add(sb, "\\nscope="); 192 + dot_label_value(sb, n->scope, labels); 193 + } 194 + if (n->target && n->target[0]) { 195 + tobi_sb_add(sb, "\\ntarget="); 196 + dot_label_value(sb, n->target, labels); 197 + } 198 + if (n->str && n->str[0] && (n->name || n->path)) { 199 + tobi_sb_add(sb, "\\nstr="); 200 + dot_label_value(sb, n->str, labels); 201 + } 202 + if (n->has_source && n->source) { 203 + tobi_sb_add(sb, "\\nsource="); 204 + dot_label_value(sb, n->source, labels); 205 + } 206 + if (n->value) { 207 + tobi_sb_printf(sb, "\\nvalue=0x%llx", (unsigned long long)n->value); 208 + } 209 + if (n->bit_len || n->bit_off) { 210 + tobi_sb_printf(sb, "\\nbits=%llu:%llu", (unsigned long long)n->bit_off, 211 + (unsigned long long)n->bit_len); 212 + } 168 213 } 169 214 tobi_sb_printf(sb, "\\noff=0x%zx", n->off); 215 + if (labels == TOBI_GRAPH_LABEL_FULL) { 216 + tobi_sb_printf(sb, "\\nlen=%zu", n->len); 217 + } 170 218 if (n->kind == TOBI_IR_UNKNOWN) { 171 219 tobi_sb_printf(sb, "\\nop=0x%x", n->raw_op); 172 220 } ··· 175 223 static size_t dot_stmt(tobi_sb *sb, const tobi_ir *n, dot_ctx *ctx) { 176 224 size_t id = ctx->stmt_next++; 177 225 tobi_sb_printf(sb, " n%zu [label=\"", id); 178 - dot_ir_label(sb, n); 226 + dot_ir_label(sb, n, ctx->labels); 179 227 tobi_sb_add(sb, "\"];\n"); 180 228 if (n->kind == TOBI_IR_METHOD && n->child_len > 0) { 181 229 (void)dot_block(sb, n->child[0], id, "entry", ctx, NULL); ··· 209 257 tobi_sb_add(sb, " edge [color=\"#000000\",fontcolor=\"#000000\",arrowsize=0.75,penwidth=1.1];\n"); 210 258 } 211 259 212 - static char *dot_cfg(const tobi_ir *root) { 260 + static char *dot_cfg(const tobi_ir *root, tobi_graph_labels labels) { 213 261 tobi_sb sb; 214 262 tobi_sb_init(&sb); 215 263 tobi_sb_add(&sb, "digraph tobi_cfg {\n"); 216 264 dot_common_attrs(&sb); 217 265 if (root) { 218 - dot_ctx ctx = {0, 0}; 266 + dot_ctx ctx = {0, 0, labels}; 219 267 size_t root_id = dot_stmt(&sb, root, &ctx); 220 268 size_t prev = root_id; 221 269 for (size_t i = 0; i < root->child_len; i++) { ··· 231 279 static size_t dot_ast_node(tobi_sb *sb, const tobi_ir *n, dot_ctx *ctx) { 232 280 size_t id = ctx->stmt_next++; 233 281 tobi_sb_printf(sb, " n%zu [label=\"", id); 234 - dot_ir_label(sb, n); 282 + dot_ir_label(sb, n, ctx->labels); 235 283 tobi_sb_add(sb, "\"];\n"); 236 284 for (size_t i = 0; i < n->child_len; i++) { 237 285 size_t child_id = dot_ast_node(sb, n->child[i], ctx); ··· 240 288 return id; 241 289 } 242 290 243 - static char *dot_ast(const tobi_ir *root) { 291 + static char *dot_ast(const tobi_ir *root, tobi_graph_labels labels) { 244 292 tobi_sb sb; 245 293 tobi_sb_init(&sb); 246 294 tobi_sb_add(&sb, "digraph tobi_ast {\n"); 247 295 dot_common_attrs(&sb); 248 296 if (root) { 249 - dot_ctx ctx = {0, 0}; 297 + dot_ctx ctx = {0, 0, labels}; 250 298 (void)dot_ast_node(&sb, root, &ctx); 251 299 } 252 300 tobi_sb_add(&sb, "}\n"); ··· 266 314 return 0; 267 315 } 268 316 269 - static char *dot_namespace(const tobi_ns *ns) { 317 + static int ns_index_n(const tobi_ns *ns, const char *path, size_t len, size_t *idx) { 318 + if (!ns || !path) { 319 + return 0; 320 + } 321 + for (size_t i = 0; i < ns->len; i++) { 322 + const char *cur = ns->items[i].path; 323 + if (cur && strlen(cur) == len && strncmp(cur, path, len) == 0) { 324 + *idx = i; 325 + return 1; 326 + } 327 + } 328 + return 0; 329 + } 330 + 331 + static int ns_index_for_ir(const tobi_ns *ns, const tobi_ir *n, tobi_ns_kind kind, size_t *idx) { 332 + if (!ns || !n) { 333 + return 0; 334 + } 335 + if (n->path && ns_index(ns, n->path, idx)) { 336 + return 1; 337 + } 338 + if (!n->name) { 339 + return 0; 340 + } 341 + for (size_t i = 0; i < ns->len; i++) { 342 + const tobi_ns_ent *e = &ns->items[i]; 343 + if (e->kind == kind && e->name && strcmp(e->name, n->name) == 0 && e->off == n->off) { 344 + *idx = i; 345 + return 1; 346 + } 347 + } 348 + return 0; 349 + } 350 + 351 + static void dot_ns_edge(tobi_sb *sb, size_t *edge_next, size_t from, size_t to, const char *style, 352 + const char *label, int constraint, tobi_graph_labels labels) { 353 + const char *edge_style = style && style[0] ? style : "solid"; 354 + if (labels != TOBI_GRAPH_LABEL_NONE && label && label[0]) { 355 + size_t id = (*edge_next)++; 356 + tobi_sb_printf(sb, " r%zu [shape=box,style=\"solid\",", id); 357 + tobi_sb_add(sb, "color=\"#000000\",fontcolor=\"#000000\","); 358 + tobi_sb_add(sb, "fontname=\"Times-Roman\",fontsize=10,margin=\"0.06,0.03\","); 359 + tobi_sb_add(sb, "height=0,width=0,label=\""); 360 + dot_escape(sb, label); 361 + tobi_sb_add(sb, "\"];\n"); 362 + tobi_sb_printf(sb, " n%zu -> r%zu [style=%s,arrowhead=none", from, id, edge_style); 363 + if (!constraint) { 364 + tobi_sb_add(sb, ",constraint=false"); 365 + } 366 + tobi_sb_add(sb, "];\n"); 367 + tobi_sb_printf(sb, " r%zu -> n%zu [style=%s,arrowhead=vee", id, to, edge_style); 368 + if (!constraint) { 369 + tobi_sb_add(sb, ",constraint=false"); 370 + } 371 + tobi_sb_add(sb, "];\n"); 372 + return; 373 + } 374 + tobi_sb_printf(sb, " n%zu -> n%zu [style=%s,arrowhead=vee", from, to, edge_style); 375 + if (!constraint) { 376 + tobi_sb_add(sb, ",constraint=false"); 377 + } 378 + tobi_sb_add(sb, "];\n"); 379 + } 380 + 381 + static int target_ns_index(const tobi_ns *ns, const char *target, const char *key, size_t *idx) { 382 + if (!target || !key) { 383 + return 0; 384 + } 385 + size_t key_len = strlen(key); 386 + const char *p = target; 387 + while ((p = strstr(p, key)) != NULL) { 388 + if (p != target && p[-1] != ' ' && p[-1] != '\t') { 389 + p += key_len; 390 + continue; 391 + } 392 + const char *value = p + key_len; 393 + const char *end = value; 394 + while (*end && *end != ' ' && *end != '\t' && *end != '\n' && *end != '\r') { 395 + end++; 396 + } 397 + if (end > value && ns_index_n(ns, value, (size_t)(end - value), idx)) { 398 + return 1; 399 + } 400 + p = end; 401 + } 402 + return 0; 403 + } 404 + 405 + static void dot_namespace_field_edges(tobi_sb *sb, size_t *edge_next, const tobi_ns *ns, 406 + const tobi_ir *n, tobi_graph_labels labels) { 407 + static const struct { 408 + const char *key; 409 + const char *label; 410 + } rels[] = { 411 + {"region=", "region"}, 412 + {"index=", "index"}, 413 + {"data=", "data"}, 414 + {"bank=", "bank"} 415 + }; 416 + size_t from = 0; 417 + if (!ns_index_for_ir(ns, n, TOBI_NS_FIELD, &from)) { 418 + return; 419 + } 420 + for (size_t i = 0; i < sizeof(rels) / sizeof(rels[0]); i++) { 421 + size_t to = 0; 422 + if (target_ns_index(ns, n->target, rels[i].key, &to)) { 423 + dot_ns_edge(sb, edge_next, from + 1u, to + 1u, "dotted", rels[i].label, 0, labels); 424 + } 425 + } 426 + } 427 + 428 + static void dot_namespace_ir_edges(tobi_sb *sb, size_t *edge_next, const tobi_ir *n, const tobi_ns *ns, 429 + size_t current_method, tobi_graph_labels labels) { 430 + if (!n || !ns) { 431 + return; 432 + } 433 + if (n->kind == TOBI_IR_METHOD && n->path) { 434 + size_t found = 0; 435 + if (ns_index(ns, n->path, &found)) { 436 + current_method = found; 437 + } 438 + } 439 + if (n->kind == TOBI_IR_CALL && current_method != (size_t)-1 && n->path) { 440 + size_t target = 0; 441 + if (ns_index(ns, n->path, &target) && target != current_method) { 442 + dot_ns_edge(sb, edge_next, current_method + 1u, target + 1u, "dashed", "call", 0, labels); 443 + } 444 + } else if (n->kind == TOBI_IR_FIELD_ELEM) { 445 + dot_namespace_field_edges(sb, edge_next, ns, n, labels); 446 + } 447 + for (size_t i = 0; i < n->child_len; i++) { 448 + dot_namespace_ir_edges(sb, edge_next, n->child[i], ns, current_method, labels); 449 + } 450 + } 451 + 452 + static char *dot_namespace(const tobi_ir *root, const tobi_ns *ns, tobi_graph_labels labels) { 270 453 tobi_sb sb; 271 454 tobi_sb_init(&sb); 272 455 tobi_sb_add(&sb, "digraph tobi_namespace {\n"); 273 456 dot_common_attrs(&sb); 274 - tobi_sb_add(&sb, " n0 [label=\"namespace\\n\\\\\"];\n"); 457 + if (labels == TOBI_GRAPH_LABEL_NONE) { 458 + tobi_sb_add(&sb, " n0 [label=\"\"];\n"); 459 + } else { 460 + tobi_sb_add(&sb, " n0 [label=\"namespace\\n\\\\\"];\n"); 461 + } 275 462 if (ns) { 276 463 for (size_t i = 0; i < ns->len; i++) { 277 464 const tobi_ns_ent *e = &ns->items[i]; 278 - tobi_sb_printf(&sb, " n%zu [label=\"%s", i + 1u, tobi_ns_kind_name(e->kind)); 279 - if (e->name && e->name[0]) { 280 - tobi_sb_add(&sb, "\\n"); 281 - dot_label_text(&sb, e->name); 465 + tobi_sb_printf(&sb, " n%zu [label=\"", i + 1u); 466 + if (labels != TOBI_GRAPH_LABEL_NONE) { 467 + tobi_sb_add(&sb, tobi_ns_kind_name(e->kind)); 468 + if (e->name && e->name[0]) { 469 + tobi_sb_add(&sb, "\\n"); 470 + dot_label_value(&sb, e->name, labels); 471 + } 472 + if (e->path && e->path[0]) { 473 + tobi_sb_add(&sb, "\\n"); 474 + dot_label_value(&sb, e->path, labels); 475 + } 476 + tobi_sb_printf(&sb, "\\noff=0x%zx", e->off); 477 + if (labels == TOBI_GRAPH_LABEL_FULL) { 478 + if (e->owner && e->owner[0]) { 479 + tobi_sb_add(&sb, "\\nowner="); 480 + dot_label_value(&sb, e->owner, labels); 481 + } 482 + if (e->target && e->target[0]) { 483 + tobi_sb_add(&sb, "\\ntarget="); 484 + dot_label_value(&sb, e->target, labels); 485 + } 486 + tobi_sb_printf(&sb, "\\nargs=%u flags=0x%x", e->args, e->flags); 487 + if (e->external) { 488 + tobi_sb_add(&sb, "\\nexternal=true"); 489 + } 490 + if (e->has_region_range) { 491 + tobi_sb_printf(&sb, "\\nregion=0x%llx+0x%llx", 492 + (unsigned long long)e->region_offset, 493 + (unsigned long long)e->region_len); 494 + } 495 + if (e->has_source && e->source) { 496 + tobi_sb_add(&sb, "\\nsource="); 497 + dot_label_value(&sb, e->source, labels); 498 + } 499 + } 282 500 } 283 - if (e->path && e->path[0]) { 284 - tobi_sb_add(&sb, "\\n"); 285 - dot_label_text(&sb, e->path); 501 + tobi_sb_add(&sb, "\""); 502 + if (e->external || e->kind == TOBI_NS_EXTERNAL) { 503 + tobi_sb_add(&sb, ",style=\"dashed\""); 286 504 } 287 - tobi_sb_printf(&sb, "\\noff=0x%zx\"];\n", e->off); 505 + tobi_sb_add(&sb, "];\n"); 288 506 } 507 + size_t edge_next = 0; 289 508 for (size_t i = 0; i < ns->len; i++) { 290 509 const tobi_ns_ent *e = &ns->items[i]; 291 510 size_t parent = 0; ··· 293 512 if (e->owner && ns_index(ns, e->owner, &found)) { 294 513 parent = found + 1u; 295 514 } 296 - tobi_sb_printf(&sb, " n%zu -> n%zu;\n", parent, i + 1u); 515 + if (e->external || e->kind == TOBI_NS_EXTERNAL) { 516 + dot_ns_edge(&sb, &edge_next, parent, i + 1u, "dotted", "external", 1, labels); 517 + } else { 518 + tobi_sb_printf(&sb, " n%zu -> n%zu;\n", parent, i + 1u); 519 + } 297 520 if (e->target && ns_index(ns, e->target, &found)) { 298 - tobi_sb_printf(&sb, " n%zu -> n%zu [style=dashed,arrowhead=vee];\n", i + 1u, found + 1u); 521 + dot_ns_edge(&sb, &edge_next, i + 1u, found + 1u, "dashed", "alias", 0, labels); 299 522 } 300 523 } 524 + dot_namespace_ir_edges(&sb, &edge_next, root, ns, (size_t)-1, labels); 301 525 } 302 526 tobi_sb_add(&sb, "}\n"); 303 527 return tobi_sb_take(&sb); 304 528 } 305 529 306 530 char *tobi_cf_dot(const tobi_ir *root) { 307 - return dot_cfg(root); 531 + return dot_cfg(root, TOBI_GRAPH_LABEL_SHORT); 308 532 } 309 533 310 534 char *tobi_graph_dot(const tobi_ir *root, const tobi_ns *ns, tobi_graph_kind kind) { 535 + return tobi_graph_dot_labels(root, ns, kind, TOBI_GRAPH_LABEL_SHORT); 536 + } 537 + 538 + char *tobi_graph_dot_labels(const tobi_ir *root, const tobi_ns *ns, tobi_graph_kind kind, 539 + tobi_graph_labels labels) { 311 540 switch (kind) { 312 541 case TOBI_GRAPH_AST: 313 - return dot_ast(root); 542 + return dot_ast(root, labels); 314 543 case TOBI_GRAPH_NAMESPACE: 315 - return dot_namespace(ns); 544 + return dot_namespace(root, ns, labels); 316 545 case TOBI_GRAPH_CFG: 317 546 default: 318 - return dot_cfg(root); 547 + return dot_cfg(root, labels); 319 548 } 320 549 }
+9
src/cf.h
··· 11 11 TOBI_GRAPH_NAMESPACE 12 12 } tobi_graph_kind; 13 13 14 + typedef enum tobi_graph_labels { 15 + TOBI_GRAPH_LABEL_SHORT, 16 + TOBI_GRAPH_LABEL_FULL, 17 + TOBI_GRAPH_LABEL_NONE 18 + } tobi_graph_labels; 19 + 14 20 int tobi_cf_recover(tobi_ir *root, tobi_diag_list *diag); 15 21 16 22 char *tobi_cf_dot(const tobi_ir *root); 17 23 18 24 char *tobi_graph_dot(const tobi_ir *root, const tobi_ns *ns, tobi_graph_kind kind); 25 + 26 + char *tobi_graph_dot_labels(const tobi_ir *root, const tobi_ns *ns, tobi_graph_kind kind, 27 + tobi_graph_labels labels); 19 28 20 29 #endif
+31 -1
src/main.c
··· 23 23 " --json print parsed ir as json\n" 24 24 " --dot print recovered control flow as Graphviz DOT\n" 25 25 " --graph K print DOT graph: cfg, ast, namespace\n" 26 + " --graph-labels K\n" 27 + " set graph labels: short, full, none\n" 26 28 " --raw print input/table metadata and aml offsets\n" 27 29 " -o FILE write primary output to FILE instead of stdout\n" 28 30 " --strict treat malformed or unsupported constructs as hard errors\n" ··· 169 171 return 0; 170 172 } 171 173 174 + static int parse_graph_labels(const char *s, tobi_graph_labels *labels) { 175 + if (strcmp(s, "short") == 0) { 176 + *labels = TOBI_GRAPH_LABEL_SHORT; 177 + return 1; 178 + } 179 + if (strcmp(s, "full") == 0) { 180 + *labels = TOBI_GRAPH_LABEL_FULL; 181 + return 1; 182 + } 183 + if (strcmp(s, "none") == 0) { 184 + *labels = TOBI_GRAPH_LABEL_NONE; 185 + return 1; 186 + } 187 + return 0; 188 + } 189 + 172 190 static void print_diags(const tobi_diag_list *dl, int plain) { 173 191 int colour = !plain && isatty(fileno(stderr)); 174 192 for (size_t i = 0; i < dl->len; i++) { ··· 236 254 int stdin_count = 0; 237 255 const char *output_path = NULL; 238 256 tobi_graph_kind graph_kind = TOBI_GRAPH_CFG; 257 + tobi_graph_labels graph_labels = TOBI_GRAPH_LABEL_SHORT; 239 258 const char **files = NULL; 240 259 size_t file_len = 0; 241 260 size_t file_cap = 0; ··· 267 286 return 2; 268 287 } 269 288 mode_graph = 1; 289 + } else if (strcmp(argv[i], "--graph-labels") == 0) { 290 + if (i + 1 >= argc) { 291 + fprintf(stderr, "tobi: --graph-labels requires short, full, or none\n"); 292 + free(files); 293 + return 2; 294 + } 295 + if (!parse_graph_labels(argv[++i], &graph_labels)) { 296 + fprintf(stderr, "tobi: unknown graph label mode %s\n", argv[i]); 297 + free(files); 298 + return 2; 299 + } 270 300 } else if (strcmp(argv[i], "--raw") == 0) { 271 301 mode_raw = 1; 272 302 } else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) { ··· 378 408 fputs(s, outfp); 379 409 free(s); 380 410 } else if (mode_graph) { 381 - char *s = tobi_graph_dot(res.root, &res.ns, graph_kind); 411 + char *s = tobi_graph_dot_labels(res.root, &res.ns, graph_kind, graph_labels); 382 412 fputs(s, outfp); 383 413 free(s); 384 414 } else {
+56
test/t_cf.c
··· 59 59 T_STR(dot, "LL00"); 60 60 T_STR(dot, "method"); 61 61 free(dot); 62 + dot = tobi_graph_dot_labels(r.root, &r.ns, TOBI_GRAPH_AST, TOBI_GRAPH_LABEL_FULL); 63 + T_STR(dot, "source=raw"); 64 + T_STR(dot, "len="); 65 + free(dot); 66 + dot = tobi_graph_dot_labels(r.root, &r.ns, TOBI_GRAPH_CFG, TOBI_GRAPH_LABEL_NONE); 67 + T_STR(dot, "digraph tobi_cfg"); 68 + T_CHECK(strstr(dot, "LL00") == NULL); 69 + T_CHECK(strstr(dot, "off=0x") == NULL); 70 + free(dot); 71 + tobi_parse_result_free(&r); 72 + 73 + uint8_t ns_call[] = { 74 + 0x14,0x08,'C','A','L','0',0x01,0xa4,0x68, 75 + 0x14,0x0d,'U','S','E','0',0x00,0xa4,'C','A','L','0',0x0a,0x05 76 + }; 77 + T_CHECK(tobi_parse(ns_call, sizeof(ns_call), 0, &r)); 78 + T_CHECK(tobi_cf_recover(r.root, &r.diag)); 79 + dot = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_NAMESPACE); 80 + T_STR(dot, "USE0"); 81 + T_STR(dot, "CAL0"); 82 + T_STR(dot, "call"); 83 + free(dot); 84 + tobi_parse_result_free(&r); 85 + 86 + uint8_t ns_field[] = { 87 + 0x5b,0x80,'R','E','G','0',0x01,0x0a,0x10,0x0a,0x04, 88 + 0x5b,0x81,0x0b,'R','E','G','0',0x00,'F','L','D','0',0x08 89 + }; 90 + T_CHECK(tobi_parse(ns_field, sizeof(ns_field), 0, &r)); 91 + T_CHECK(tobi_cf_recover(r.root, &r.diag)); 92 + dot = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_NAMESPACE); 93 + T_STR(dot, "FLD0"); 94 + T_STR(dot, "REG0"); 95 + T_STR(dot, "region"); 96 + free(dot); 97 + tobi_parse_result_free(&r); 98 + 99 + uint8_t ns_alias[] = { 100 + 0x08,'C','A','L','0',0x01, 101 + 0x06,'C','A','L','0','A','L','S','0' 102 + }; 103 + T_CHECK(tobi_parse(ns_alias, sizeof(ns_alias), 0, &r)); 104 + T_CHECK(tobi_cf_recover(r.root, &r.diag)); 105 + dot = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_NAMESPACE); 106 + T_STR(dot, "ALS0"); 107 + T_STR(dot, "alias"); 108 + free(dot); 109 + tobi_parse_result_free(&r); 110 + 111 + uint8_t ns_external[] = {0x15,'C','A','L','X',0x08,0x01}; 112 + T_CHECK(tobi_parse(ns_external, sizeof(ns_external), 0, &r)); 113 + T_CHECK(tobi_cf_recover(r.root, &r.diag)); 114 + dot = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_NAMESPACE); 115 + T_STR(dot, "CALX"); 116 + T_STR(dot, "external"); 117 + free(dot); 62 118 tobi_parse_result_free(&r); 63 119 64 120 uint8_t amb[] = {0xa0,0x03,0x01,0xa1};
+17
test/t_cli.c
··· 220 220 T_STR(s, "digraph tobi_namespace"); 221 221 T_STR(s, "MTH0"); 222 222 free(s); 223 + const char *argv_full_labels[] = {TOBI_BIN, "--graph", "ast", "--graph-labels", "full", path, NULL}; 224 + s = run_capture_argv(argv_full_labels, NULL, 0, &st); 225 + T_CHECK(st == 0); 226 + T_STR(s, "source="); 227 + T_STR(s, "len="); 228 + free(s); 229 + const char *argv_no_labels[] = {TOBI_BIN, "--dot", "--graph-labels", "none", path, NULL}; 230 + s = run_capture_argv(argv_no_labels, NULL, 0, &st); 231 + T_CHECK(st == 0); 232 + T_STR(s, "digraph tobi_cfg"); 233 + T_CHECK(strstr(s, "MTH0") == NULL); 234 + T_CHECK(strstr(s, "off=0x") == NULL); 235 + free(s); 223 236 { 224 237 const char *out_path = "/tmp/tobi_cli_graph.dot"; 225 238 const char *argv_out[] = {TOBI_BIN, "--dot", path, "-o", out_path, NULL}; ··· 325 338 free(s); 326 339 const char *argv_bad_graph[] = {TOBI_BIN, "--graph", "nope", path, NULL}; 327 340 s = run_capture_argv(argv_bad_graph, NULL, 0, &st); 341 + T_CHECK(st == 2); 342 + free(s); 343 + const char *argv_bad_graph_labels[] = {TOBI_BIN, "--graph-labels", "verbose", path, NULL}; 344 + s = run_capture_argv(argv_bad_graph_labels, NULL, 0, &st); 328 345 T_CHECK(st == 2); 329 346 free(s); 330 347 const char *argv_missing_graph[] = {TOBI_BIN, "--graph", NULL};
+72 -6
test/t_corpus.c
··· 9 9 #include <dirent.h> 10 10 #include <errno.h> 11 11 #include <sys/stat.h> 12 + #include <unistd.h> 12 13 13 14 #ifndef TOBI_SRC_DIR 14 15 #define TOBI_SRC_DIR "." ··· 43 44 } 44 45 fclose(fp); 45 46 return 1; 47 + } 48 + 49 + static int write_blob(const char *path, const uint8_t *data, size_t len) { 50 + FILE *fp = fopen(path, "wb"); 51 + if (!fp) { 52 + fprintf(stderr, "open %s: %s\n", path, strerror(errno)); 53 + return 0; 54 + } 55 + if (len && fwrite(data, 1, len, fp) != len) { 56 + fclose(fp); 57 + return 0; 58 + } 59 + return fclose(fp) == 0; 46 60 } 47 61 48 62 static int stable_emit_blob(const char *label, const uint8_t *data, size_t len, int strict_should_pass) { ··· 112 126 return rc; 113 127 } 114 128 115 - static int walk_dir(const char *rel, size_t *count, size_t *bad_count) { 116 - char dir_path[512]; 117 - snprintf(dir_path, sizeof(dir_path), "%s/%s", TOBI_SRC_DIR, rel); 129 + static int has_aml_suffix(const char *name) { 130 + size_t len = strlen(name); 131 + return len >= 4u && strcmp(name + len - 4u, ".aml") == 0; 132 + } 133 + 134 + static int walk_dir_path(const char *dir_path, size_t *count, size_t *bad_count) { 118 135 DIR *dir = opendir(dir_path); 119 136 T_CHECK(dir != NULL); 120 137 struct dirent *de = NULL; ··· 122 139 if (de->d_name[0] == '.') { 123 140 continue; 124 141 } 125 - char path[768]; 126 - snprintf(path, sizeof(path), "%s/%s", dir_path, de->d_name); 142 + char path[1024]; 143 + int n = snprintf(path, sizeof(path), "%s/%s", dir_path, de->d_name); 144 + T_CHECK(n > 0 && (size_t)n < sizeof(path)); 127 145 struct stat st; 128 146 T_CHECK(stat(path, &st) == 0); 129 - if (S_ISREG(st.st_mode)) { 147 + if (S_ISDIR(st.st_mode)) { 148 + T_CHECK(walk_dir_path(path, count, bad_count) == 0); 149 + } else if (S_ISREG(st.st_mode) && has_aml_suffix(de->d_name)) { 130 150 int bad = strstr(de->d_name, "bad") != NULL; 131 151 T_CHECK(stable_emit(path, !bad) == 0); 132 152 (*count)++; ··· 139 159 return 0; 140 160 } 141 161 162 + static int walk_dir(const char *rel, size_t *count, size_t *bad_count) { 163 + char dir_path[512]; 164 + int n = snprintf(dir_path, sizeof(dir_path), "%s/%s", TOBI_SRC_DIR, rel); 165 + T_CHECK(n > 0 && (size_t)n < sizeof(dir_path)); 166 + return walk_dir_path(dir_path, count, bad_count); 167 + } 168 + 169 + static int walk_optional_external_dir(size_t *count, size_t *bad_count) { 170 + const char *dir = getenv("TOBI_AML_CORPUS"); 171 + if (!dir || !dir[0]) { 172 + dir = getenv("TOBI_CORPUS"); 173 + } 174 + if (!dir || !dir[0]) { 175 + return 0; 176 + } 177 + size_t before = *count; 178 + T_CHECK(walk_dir_path(dir, count, bad_count) == 0); 179 + T_CHECK(*count > before); 180 + return 0; 181 + } 182 + 183 + static int exercise_external_discovery(void) { 184 + char dir[] = "/tmp/tobi-corpus-XXXXXX"; 185 + T_CHECK(mkdtemp(dir) != NULL); 186 + char nested[256]; 187 + int n = snprintf(nested, sizeof(nested), "%s/nested", dir); 188 + T_CHECK(n > 0 && (size_t)n < sizeof(nested)); 189 + T_CHECK(mkdir(nested, 0700) == 0); 190 + char path[256]; 191 + n = snprintf(path, sizeof(path), "%s/m0.aml", nested); 192 + T_CHECK(n > 0 && (size_t)n < sizeof(path)); 193 + uint8_t m0[] = {0x14,0x0c,'M','T','H','0',0x01,0x70,0x0a,0x2a,0x60,0xa4,0x60}; 194 + T_CHECK(write_blob(path, m0, sizeof(m0))); 195 + size_t count = 0; 196 + size_t bad_count = 0; 197 + T_CHECK(walk_dir_path(dir, &count, &bad_count) == 0); 198 + T_CHECK(count == 1); 199 + T_CHECK(bad_count == 0); 200 + T_CHECK(unlink(path) == 0); 201 + T_CHECK(rmdir(nested) == 0); 202 + T_CHECK(rmdir(dir) == 0); 203 + return 0; 204 + } 205 + 142 206 int t_corpus(void) { 143 207 size_t count = 0; 144 208 size_t bad_count = 0; 209 + T_CHECK(exercise_external_discovery() == 0); 145 210 T_CHECK(walk_dir("sam", &count, &bad_count) == 0); 146 211 T_CHECK(walk_dir("fuzz/seed", &count, &bad_count) == 0); 212 + T_CHECK(walk_optional_external_dir(&count, &bad_count) == 0); 147 213 T_CHECK(count >= 9); 148 214 T_CHECK(bad_count >= 2); 149 215 return 0;
+61
test/t_snap.c
··· 68 68 "}\n"; 69 69 T_CHECK(strcmp(d, dwant) == 0); 70 70 free(d); 71 + 72 + char *g = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_CFG); 73 + const char *cfg_want = 74 + "digraph tobi_cfg {\n" 75 + " graph [rankdir=TB,bgcolor=\"#ffffff\",pad=0.18,nodesep=0.5,ranksep=0.72,splines=ortho,outputorder=edgesfirst];\n" 76 + " node [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Courier\",fontsize=12,margin=\"0.12,0.07\",penwidth=1.1];\n" 77 + " edge [color=\"#000000\",fontcolor=\"#000000\",arrowsize=0.75,penwidth=1.1];\n" 78 + " n0 [label=\"root\\noff=0x0\"];\n" 79 + " n1 [label=\"method\\nMTH0\\noff=0x0\"];\n" 80 + " n2 [label=\"store\\noff=0x7\"];\n" 81 + " e0 [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Times-Roman\",fontsize=10,margin=\"0.06,0.03\",height=0,width=0,label=\"entry\"];\n" 82 + " n1 -> e0 [arrowhead=none, weight=2];\n" 83 + " e0 -> n2 [weight=2];\n" 84 + " n3 [label=\"return\\noff=0xb\"];\n" 85 + " e1 [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Times-Roman\",fontsize=10,margin=\"0.06,0.03\",height=0,width=0,label=\"next\"];\n" 86 + " n2 -> e1 [arrowhead=none, weight=2];\n" 87 + " e1 -> n3 [weight=2];\n" 88 + " e2 [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Times-Roman\",fontsize=10,margin=\"0.06,0.03\",height=0,width=0,label=\"entry\"];\n" 89 + " n0 -> e2 [arrowhead=none, weight=2];\n" 90 + " e2 -> n1 [weight=2];\n" 91 + "}\n"; 92 + T_CHECK(strcmp(g, cfg_want) == 0); 93 + free(g); 94 + 95 + g = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_AST); 96 + const char *ast_want = 97 + "digraph tobi_ast {\n" 98 + " graph [rankdir=TB,bgcolor=\"#ffffff\",pad=0.18,nodesep=0.5,ranksep=0.72,splines=ortho,outputorder=edgesfirst];\n" 99 + " node [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Courier\",fontsize=12,margin=\"0.12,0.07\",penwidth=1.1];\n" 100 + " edge [color=\"#000000\",fontcolor=\"#000000\",arrowsize=0.75,penwidth=1.1];\n" 101 + " n0 [label=\"root\\noff=0x0\"];\n" 102 + " n1 [label=\"method\\nMTH0\\noff=0x0\"];\n" 103 + " n2 [label=\"block\\noff=0x7\"];\n" 104 + " n3 [label=\"store\\noff=0x7\"];\n" 105 + " n4 [label=\"integer\\noff=0x8\"];\n" 106 + " n3 -> n4;\n" 107 + " n5 [label=\"ref\\nLocal0\\noff=0xa\"];\n" 108 + " n3 -> n5;\n" 109 + " n2 -> n3;\n" 110 + " n6 [label=\"return\\noff=0xb\"];\n" 111 + " n7 [label=\"ref\\nLocal0\\noff=0xc\"];\n" 112 + " n6 -> n7;\n" 113 + " n2 -> n6;\n" 114 + " n1 -> n2;\n" 115 + " n0 -> n1;\n" 116 + "}\n"; 117 + T_CHECK(strcmp(g, ast_want) == 0); 118 + free(g); 119 + 120 + g = tobi_graph_dot(r.root, &r.ns, TOBI_GRAPH_NAMESPACE); 121 + const char *ns_want = 122 + "digraph tobi_namespace {\n" 123 + " graph [rankdir=TB,bgcolor=\"#ffffff\",pad=0.18,nodesep=0.5,ranksep=0.72,splines=ortho,outputorder=edgesfirst];\n" 124 + " node [shape=box,style=\"solid\",color=\"#000000\",fontcolor=\"#000000\",fontname=\"Courier\",fontsize=12,margin=\"0.12,0.07\",penwidth=1.1];\n" 125 + " edge [color=\"#000000\",fontcolor=\"#000000\",arrowsize=0.75,penwidth=1.1];\n" 126 + " n0 [label=\"namespace\\n\\\\\"];\n" 127 + " n1 [label=\"method\\nMTH0\\n\\\\MTH0\\noff=0x0\"];\n" 128 + " n0 -> n1;\n" 129 + "}\n"; 130 + T_CHECK(strcmp(g, ns_want) == 0); 131 + free(g); 71 132 tobi_parse_result_free(&r); 72 133 73 134 uint8_t nested[] = {