1 /* 2 * Copyright (C) 2026 Jo-Philipp Wich <jo@mein.io> 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ANY IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <stdlib.h> 18 #include <string.h> 19 #include <stdint.h> 20 #include <stdarg.h> 21 #include <inttypes.h> 22 #include <regex.h> 23 24 #include "debug_highlight.h" 25 26 /* -- styling, ported verbatim from the pre-protocol lib/debug.c ---------- */ 27 28 enum { 29 BOLD = (1 << 0), 30 FAINT = (1 << 1), 31 ULINE = (1 << 2), 32 }; 33 34 typedef enum { 35 FG_NONE = 0, 36 FG_BLACK = 30, 37 FG_RED = 31, 38 FG_GREEN = 32, 39 FG_YELLOW = 33, 40 FG_BLUE = 34, 41 FG_MAGENTA = 35, 42 FG_CYAN = 36, 43 FG_GRAY = 37, 44 FG_BBLACK = 90, 45 FG_BRED = 91, 46 FG_BGREEN = 92, 47 FG_BYELLOW = 93, 48 FG_BBLUE = 94, 49 FG_BMAGENT = 95, 50 FG_BCYAN = 96, 51 FG_BWHITE = 97, 52 } fg_color_t; 53 54 typedef enum { 55 BG_NONE = 0, 56 BG_BLACK = 40, 57 BG_GRAY = 100, 58 } bg_color_t; 59 60 typedef struct { 61 fg_color_t fg; 62 bg_color_t bg; 63 unsigned int styles; 64 } style_t; 65 66 static void 67 cs(FILE *out, const style_t *style) 68 { 69 int codes[8] = { 0 }; 70 size_t i = 0; 71 72 if (style == NULL) { 73 fputs("\033[0m", out); 74 return; 75 } 76 77 if ((style->styles & (BOLD | FAINT | ULINE)) == 0) 78 codes[i++] = 0; 79 80 if (style->styles & BOLD) codes[i++] = 1; 81 if (style->styles & FAINT) codes[i++] = 2; 82 if (style->styles & ULINE) codes[i++] = 4; 83 84 codes[i++] = style->fg ? style->fg : 39; 85 codes[i++] = style->bg ? style->bg : 49; 86 87 fputs("\033[", out); 88 89 for (size_t n = 0; n < i; n++) 90 fprintf(out, "%s%d", n ? ";" : "", codes[n]); 91 92 fputc('m', out); 93 } 94 95 /* -- syntax highlighting rules, ported verbatim -------------------------- */ 96 97 static struct { 98 fg_color_t color; 99 const char *start, *end; 100 } highlight_rules[] = { 101 { FG_GRAY, "^#!.*", NULL }, 102 103 /* declarations */ 104 { FG_GREEN, "\\<(let|const|function|this)\\>", NULL }, 105 106 /* arrow functions */ 107 { FG_GREEN, "(\\<\\w+\\>|\\([[:alnum:][:space:]_,.]*\\))[[:space:]]*=>", NULL }, 108 109 /* flow control */ 110 { FG_BYELLOW, "\\<(while|if|else|elif|switch|case|default|for|in|endif|endfor|endwhile|endfunction)\\>", NULL }, 111 112 /* keywords */ 113 { FG_BYELLOW, "\\<(export|import|try|catch|delete)\\>", NULL }, 114 115 /* exit points */ 116 { FG_MAGENTA, "\\<(break|continue|return)\\>", NULL }, 117 118 /* numeric literals */ 119 { FG_CYAN, "\\<([0-9]+\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\\>", NULL }, 120 { FG_CYAN, "\\<0[xX][[:xdigit:]]+(\\.[[:xdigit:]]+)?\\>", NULL }, 121 { FG_CYAN, "\\<(0[oO][0-7]+|0[bB][01]+|[0-9]+)\\>", NULL }, 122 123 /* special values */ 124 { FG_CYAN, "\\<(true|false|null|NaN|Infinity)\\>", NULL }, 125 126 /* strings */ 127 { FG_BMAGENT, "\"([^\"\\{%#}]|\\\\.|\\{[^\"\\{%#]|[%#}][^\"\\}]|[{%#}]\\\\.)*[{%#}]?\"", NULL }, 128 { FG_BMAGENT, "'([^'\\{%#}]|\\\\.|\\{[^'\\{%#]|[%#}][^'\\}]|[{%#}]\\\\.)*[{%#}]?'", NULL }, 129 { FG_BMAGENT, "`([^`\\{%#}]|\\\\.|\\{[^`\\{%#]|[%#}][^`\\}]|[{%#}]\\\\.)*[{%#}]?`", NULL }, 130 131 /* template string expressions */ 132 { FG_BWHITE, "\\$\\{", "}" }, 133 134 /* comments */ 135 { FG_BBLUE, "(^|[[:blank:]])//.*", NULL }, 136 { FG_BBLUE, "(^|[[:space:]])/\\*", "\\*/" }, 137 { FG_BBLUE, "\\{#", "#\\}" }, 138 139 /* text outside template directives */ 140 { FG_GRAY, "[}%#]\\}", "\\{[{%#]" }, 141 { FG_GRAY, "^#!.*(\\<utpl\\>|[[:space:]]-[[:alnum:]]*T[[:alnum:]]*\\>)", "\\{[{%#]" }, 142 { FG_GRAY, "^([^{%#}]|\\{[^{%#]|[%#}][^}])+\\{[{%#]", NULL }, 143 144 /* template tags */ 145 { FG_BWHITE, "\\{[{%][+-]?|-?[%}]\\}", NULL }, 146 { FG_BBLUE, "\\{#[+-]?|-?#\\}", NULL }, 147 }; 148 149 #define NRULES (sizeof(highlight_rules) / sizeof(highlight_rules[0])) 150 151 static regex_t compiled_patterns[NRULES * 2]; 152 static bool have_highlighting = false; 153 static bool init_attempted = false; 154 155 bool 156 debug_highlight_init(void) 157 { 158 regex_t *re = NULL; 159 int err = 0; 160 size_t i; 161 162 if (init_attempted) 163 return have_highlighting; 164 165 init_attempted = true; 166 167 for (i = 0; i < NRULES; i++) { 168 re = &compiled_patterns[i * 2]; 169 err = regcomp(re, highlight_rules[i].start, REG_EXTENDED); 170 171 if (err != 0) 172 goto err; 173 174 re = &compiled_patterns[i * 2 + 1]; 175 176 if (highlight_rules[i].end) { 177 err = regcomp(re, highlight_rules[i].end, REG_EXTENDED); 178 179 if (err != 0) 180 goto err; 181 } 182 } 183 184 have_highlighting = true; 185 186 return true; 187 188 err: 189 { 190 char errbuf[128]; 191 192 regerror(err, re, errbuf, sizeof(errbuf)); 193 fprintf(stderr, "debug_highlight: regex error: %s\n", errbuf); 194 } 195 196 for (i = 0; i < NRULES * 2; i++) 197 regfree(&compiled_patterns[i]); 198 199 have_highlighting = false; 200 201 return false; 202 } 203 204 /* -- source rendering, ported from print_source_location() -------------- 205 * 206 * The original computed hl_start/hl_end/cursor_pos as byte offsets into 207 * the whole source file (it read lines off a live, seekable FILE*, so a 208 * single running byte counter was the natural coordinate space). This 209 * version instead receives an already-split line array and a per-line 210 * column range (`hl`, in the debug protocol's own {line, col} terms), so 211 * the equivalent bounds are recomputed per line instead of accumulated 212 * globally - the rendering logic itself (per-character style diffing, tab/ 213 * control-char placeholders, truncation, background shading) is otherwise 214 * unchanged. The single ULINE-underlined "current instruction" character 215 * the original also drew is dropped: the protocol only ever hands clients 216 * a statement *range*, not that finer-grained instruction position. */ 217 218 typedef struct { 219 fg_color_t color; 220 ssize_t from, to; 221 } color_span_t; 222 223 static color_span_t * 224 colors_grow(color_span_t *colors, size_t *count, size_t *cap) 225 { 226 if (*count >= *cap) { 227 size_t newcap = *cap ? *cap * 2 : 16; 228 color_span_t *p = realloc(colors, newcap * sizeof(*p)); 229 230 if (!p) 231 return colors; 232 233 colors = p; 234 *cap = newcap; 235 } 236 237 return colors; 238 } 239 240 void 241 debug_highlight_print_source(FILE *out, char **lines, size_t nlines, 242 size_t from, size_t to, 243 const debug_highlight_span_t *hl, 244 size_t left_pad, size_t columns) 245 { 246 debug_highlight_range_t range = { from, to }; 247 248 debug_highlight_print_source_ranges(out, lines, nlines, 1, &range, hl, left_pad, columns); 249 } 250 251 void 252 debug_highlight_print_source_ranges(FILE *out, char **lines, size_t nlines, 253 size_t nranges, 254 const debug_highlight_range_t *ranges, 255 const debug_highlight_span_t *hl, 256 size_t left_pad, size_t columns) 257 { 258 color_span_t *colors = NULL; 259 size_t colors_count = 0, colors_cap = 0; 260 regex_t *ml_rule_re_end = NULL; 261 fg_color_t ml_rule_color = FG_NONE; 262 style_t style = { FG_BWHITE, BG_BLACK, 0 }; 263 size_t linenum, start_line = SIZE_MAX, end_line = 0; 264 ssize_t last_indent = -1; 265 size_t r; 266 267 for (r = 0; r < nranges; r++) { 268 if (ranges[r].from == 0 || ranges[r].to == 0) 269 continue; 270 271 if (ranges[r].from < start_line) 272 start_line = ranges[r].from; 273 274 if (ranges[r].to > end_line) 275 end_line = ranges[r].to; 276 } 277 278 if (end_line > nlines) 279 end_line = nlines; 280 281 for (linenum = 1; linenum <= end_line; linenum++) { 282 const char *linestr = lines[linenum - 1]; 283 ssize_t linelen = (ssize_t)strlen(linestr); 284 size_t ml_rule_from = 0; 285 size_t line_hl_from = SIZE_MAX, line_hl_to = SIZE_MAX; 286 regmatch_t m; 287 const char *p; 288 int rf; 289 290 colors_count = 0; 291 292 /* apply highlighting rules */ 293 if (have_highlighting) { 294 size_t i; 295 296 /* single line matches */ 297 for (i = 0; i < NRULES; i++) { 298 regex_t *re = &compiled_patterns[i * 2]; 299 300 if (highlight_rules[i].end != NULL) 301 continue; 302 303 for (rf = 0, p = linestr; 304 regexec(re, p, 1, &m, rf) == 0; 305 rf = REG_NOTBOL, p += m.rm_eo) { 306 colors = colors_grow(colors, &colors_count, &colors_cap); 307 colors[colors_count++] = (color_span_t){ 308 .color = highlight_rules[i].color, 309 .from = p + m.rm_so - linestr, 310 .to = p + m.rm_eo - linestr 311 }; 312 313 if (m.rm_eo == m.rm_so) 314 break; 315 } 316 } 317 318 /* multi line matches */ 319 for (rf = 0, p = linestr, ml_rule_from = 0; 320 rf == 0 || ml_rule_re_end != NULL; 321 rf = REG_NOTBOL) { 322 323 if (ml_rule_re_end != NULL) { 324 if (regexec(ml_rule_re_end, p, 1, &m, 0) == 0) { 325 colors = colors_grow(colors, &colors_count, &colors_cap); 326 colors[colors_count++] = (color_span_t){ 327 .color = ml_rule_color, 328 .from = (ssize_t)ml_rule_from, 329 .to = p + m.rm_eo - linestr 330 }; 331 332 ml_rule_re_end = NULL; 333 ml_rule_color = FG_NONE; 334 ml_rule_from = 0; 335 p += m.rm_eo; 336 } 337 else { 338 colors = colors_grow(colors, &colors_count, &colors_cap); 339 colors[colors_count++] = (color_span_t){ 340 .color = ml_rule_color, 341 .from = (ssize_t)ml_rule_from, 342 .to = linelen 343 }; 344 345 break; 346 } 347 } 348 349 { 350 size_t i; 351 bool found = false; 352 353 for (i = 0; i < NRULES; i++) { 354 regex_t *re_start = &compiled_patterns[i * 2]; 355 regex_t *re_end = &compiled_patterns[i * 2 + 1]; 356 357 if (highlight_rules[i].end == NULL) 358 continue; 359 360 if (regexec(re_start, p, 1, &m, rf) == 0) { 361 ml_rule_re_end = re_end; 362 ml_rule_color = highlight_rules[i].color; 363 ml_rule_from = (size_t)(p + m.rm_so - linestr); 364 p += m.rm_eo; 365 found = true; 366 break; 367 } 368 } 369 370 if (!found && ml_rule_re_end == NULL) 371 break; 372 } 373 } 374 } 375 376 { 377 bool print_line = false, more_lines = false; 378 379 for (r = 0; r < nranges; r++) { 380 if (ranges[r].from == 0 || ranges[r].to == 0) 381 continue; 382 383 print_line |= (linenum >= ranges[r].from && linenum <= ranges[r].to); 384 more_lines |= (ranges[r].from > start_line && ranges[r].from == linenum + 1); 385 } 386 387 if (!print_line) { 388 if (more_lines) { 389 size_t pad = (size_t)(last_indent < 0 ? 0 : last_indent); 390 size_t i; 391 392 for (i = 0; i < left_pad; i++) 393 fputc(' ', out); 394 395 cs(out, &((style_t){ FG_GRAY, BG_BLACK, FAINT })); 396 fputs(" \xe2\x80\xa6 " /* " … " */, out); 397 398 for (i = 0; i < pad; i++) 399 fputc(' ', out); 400 401 fputs("\xe2\x80\xa6", out); 402 403 if (columns > 6 + pad) 404 for (i = 0; i < columns - 6 - pad; i++) 405 fputc(' ', out); 406 407 cs(out, NULL); 408 fputc('\n', out); 409 } 410 411 continue; 412 } 413 } 414 415 /* per-line highlight bounds, translated from the {line,col} 416 * range (see comment above) */ 417 if (hl && hl->from_line > 0 && linenum >= hl->from_line && linenum <= hl->to_line) { 418 line_hl_from = (linenum == hl->from_line) ? hl->from_col : 0; 419 line_hl_to = (linenum == hl->to_line) ? hl->to_col : SIZE_MAX; 420 } 421 422 size_t trunc = 0; 423 424 /* determine display width of line and whether it is too long */ 425 if (columns > 6) { 426 size_t c; 427 ssize_t i; 428 429 for (i = 0, c = 0; i < linelen; i++) { 430 c += (linestr[i] == '\t') ? 4 : 1; 431 432 if (c > columns - 6) { 433 trunc = (size_t)(linelen - i); 434 linelen = i; 435 break; 436 } 437 } 438 } 439 440 size_t linecols = 0; 441 ssize_t i; 442 443 for (i = 0; i < (ssize_t)left_pad; i++) 444 fputc(' ', out); 445 446 cs(out, &((style_t){ FG_GRAY, BG_BLACK, FAINT })); 447 fprintf(out, "%4zu ", linenum); 448 cs(out, &style); 449 450 for (i = 0; i < linelen; i++) { 451 style_t newstyle = { 452 .fg = FG_BWHITE, 453 .bg = ((size_t)i >= line_hl_from && (size_t)i < line_hl_to) 454 ? BG_GRAY : BG_BLACK, 455 .styles = (hl && hl->have_ip && linenum == hl->ip_line && 456 (size_t)i == hl->ip_col) ? ULINE : 0 457 }; 458 size_t j; 459 460 for (j = 0; j < colors_count; j++) 461 if (colors[j].from <= i && colors[j].to > i) 462 newstyle.fg = colors[j].color; 463 464 if (memcmp(&style, &newstyle, sizeof(style))) { 465 style = newstyle; 466 cs(out, &style); 467 } 468 469 if (linestr[i] == '\t') { 470 linecols += 4; 471 cs(out, &((style_t){ FG_BBLACK, style.bg, FAINT })); 472 fputs("<-> ", out); 473 cs(out, &style); 474 } 475 else if (linestr[i] < ' ' || linestr[i] == 0x7f) { 476 linecols++; 477 cs(out, &((style_t){ FG_BBLACK, style.bg, FAINT })); 478 fputc('.', out); 479 cs(out, &style); 480 } 481 else { 482 if (last_indent == -1) 483 last_indent = (ssize_t)linecols; 484 485 linecols++; 486 fputc(linestr[i], out); 487 } 488 } 489 490 /* reset char styles */ 491 style.styles = 0; 492 style.bg = ((size_t)linelen >= line_hl_from && (size_t)(linelen) + trunc <= line_hl_to) 493 ? BG_GRAY : BG_BLACK; 494 cs(out, &style); 495 496 if (trunc > 0) { 497 if (columns > 6 && linecols < columns - 6) 498 for (i = 0; i < (ssize_t)((columns - 6) - linecols); i++) 499 fputc(' ', out); 500 501 fputs("\xe2\x80\xa6" /* U+2026 HORIZONTAL ELLIPSIS */, out); 502 } 503 else if (columns > 5 && linecols < columns - 5) { 504 if (style.bg != BG_BLACK) { 505 style.bg = BG_BLACK; 506 cs(out, &style); 507 } 508 509 for (i = 0; i < (ssize_t)((columns - 5) - linecols); i++) 510 fputc(' ', out); 511 } 512 513 cs(out, &((style_t){ FG_NONE, BG_NONE, 0 })); 514 fputc('\n', out); 515 } 516 517 free(colors); 518 } 519 520 /* -- header bar, ported from format_context_header_backtrace()/ 521 * format_context_header_callframe() -------------------------------------- */ 522 523 /* Elide the front of `s` (in place) down to at most `maxcols` bytes, 524 * prefixing a horizontal-ellipsis marker, so the *tail* stays visible - 525 * matches the original's choice for both filenames (basename matters more 526 * than the leading directories) and call breadcrumbs (the innermost/ 527 * current frame matters more than the outermost). Byte-based rather than 528 * the original's UTF-8/ANSI-escape-aware column counting - a reasonable 529 * simplification for what is normally short, plain ASCII text (paths, 530 * identifiers). */ 531 static char * 532 truncate_head(const char *s, size_t maxcols) 533 { 534 static const char ellipsis[] = "\xe2\x80\xa6"; /* U+2026, 1 column, 3 bytes */ 535 size_t len = strlen(s); 536 char *out; 537 538 if (maxcols == 0 || len <= maxcols) 539 return strdup(s); 540 541 if (maxcols <= 1) 542 return strdup(ellipsis); 543 544 out = malloc(sizeof(ellipsis) - 1 + (maxcols - 1) + 1); 545 memcpy(out, ellipsis, sizeof(ellipsis) - 1); 546 memcpy(out + sizeof(ellipsis) - 1, s + (len - (maxcols - 1)), maxcols - 1); 547 out[sizeof(ellipsis) - 1 + (maxcols - 1)] = '\0'; 548 549 return out; 550 } 551 552 void 553 debug_highlight_print_header_bar(FILE *out, const char *bracket, const char *rest, 554 size_t left_pad, size_t columns) 555 { 556 size_t columns_avail = (columns > left_pad) ? columns - left_pad : 0; 557 size_t bracket_width = (columns_avail >= 42) ? (columns_avail - 2) / 4 : columns_avail; 558 char *bracket_trunc = columns_avail ? truncate_head(bracket, bracket_width) : strdup(bracket); 559 size_t printed = 2 + strlen(bracket_trunc); 560 size_t i; 561 562 for (i = 0; i < left_pad; i++) 563 fputc(' ', out); 564 565 cs(out, &((style_t){ FG_BWHITE, BG_GRAY, 0 })); 566 fprintf(out, "[%s]", bracket_trunc); 567 free(bracket_trunc); 568 569 if (rest && *rest && (!columns_avail || columns_avail > printed + 2 + 10)) { 570 size_t rest_width = columns_avail ? columns_avail - printed - 2 : 0; 571 char *rest_trunc = columns_avail ? truncate_head(rest, rest_width) : strdup(rest); 572 573 fprintf(out, " %s ", rest_trunc); 574 printed += 2 + strlen(rest_trunc); 575 free(rest_trunc); 576 } 577 578 if (columns_avail > printed) 579 for (i = 0; i < columns_avail - printed; i++) 580 fputc(' ', out); 581 582 cs(out, NULL); 583 fputc('\n', out); 584 } 585 586 /* -- disassembly, ported from cmd_disasm() (formerly lib/debug.c) -------- */ 587 588 /* Minimal growable byte buffer, used to build one disassembly line at a 589 * time (with real "\033[...m" sequences already embedded) so it can be 590 * measured and truncated to `columns` before being written out - mirrors 591 * what the pre-protocol version did with a uc_stringbuf_t. */ 592 typedef struct { 593 char *buf; 594 size_t len, cap; 595 } dbuf_t; 596 597 static void 598 dbuf_reserve(dbuf_t *b, size_t extra) 599 { 600 if (b->len + extra + 1 > b->cap) { 601 size_t newcap = b->cap ? b->cap * 2 : 128; 602 603 while (newcap < b->len + extra + 1) 604 newcap *= 2; 605 606 b->buf = realloc(b->buf, newcap); 607 b->cap = newcap; 608 } 609 } 610 611 static void 612 dbuf_style(dbuf_t *b, const style_t *style) 613 { 614 char tmp[32]; 615 int codes[8] = { 0 }; 616 size_t i = 0, n = 0; 617 618 if (style == NULL) { 619 dbuf_reserve(b, 4); 620 memcpy(b->buf + b->len, "\033[0m", 4); 621 b->len += 4; 622 return; 623 } 624 625 if ((style->styles & (BOLD | FAINT | ULINE)) == 0) 626 codes[i++] = 0; 627 628 if (style->styles & BOLD) codes[i++] = 1; 629 if (style->styles & FAINT) codes[i++] = 2; 630 if (style->styles & ULINE) codes[i++] = 4; 631 632 codes[i++] = style->fg ? style->fg : 39; 633 codes[i++] = style->bg ? style->bg : 49; 634 635 n += sprintf(tmp + n, "\033["); 636 637 for (size_t k = 0; k < i; k++) 638 n += sprintf(tmp + n, "%s%d", k ? ";" : "", codes[k]); 639 640 n += sprintf(tmp + n, "m"); 641 642 dbuf_reserve(b, n); 643 memcpy(b->buf + b->len, tmp, n); 644 b->len += n; 645 } 646 647 __attribute__((format(printf, 2, 3))) static void 648 dbuf_printf(dbuf_t *b, const char *fmt, ...) 649 { 650 va_list ap, ap2; 651 int n; 652 653 va_start(ap, fmt); 654 va_copy(ap2, ap); 655 n = vsnprintf(NULL, 0, fmt, ap2); 656 va_end(ap2); 657 658 if (n > 0) { 659 dbuf_reserve(b, (size_t)n); 660 vsnprintf(b->buf + b->len, (size_t)n + 1, fmt, ap); 661 b->len += (size_t)n; 662 } 663 664 va_end(ap); 665 } 666 667 /* Byte-based visible-width truncation with a trailing ellipsis, skipping 668 * embedded "\033[...m" escape sequences when counting columns - the same 669 * "reasonable simplification" truncate_head() above documents, since 670 * disassembly text (mnemonics, hex, identifiers) is normally plain ASCII. */ 671 static void 672 dbuf_truncate_tail(dbuf_t *b, size_t maxcols) 673 { 674 size_t col = 0, i = 0, cut = SIZE_MAX; 675 676 if (maxcols == 0) 677 return; 678 679 while (i < b->len) { 680 if (b->buf[i] == '\033') { 681 size_t j = i + 1; 682 683 if (j < b->len && b->buf[j] == '[') { 684 j++; 685 686 while (j < b->len && b->buf[j] != 'm') 687 j++; 688 689 if (j < b->len) 690 j++; 691 } 692 693 i = j; 694 continue; 695 } 696 697 if (col + 1 == maxcols && cut == SIZE_MAX) 698 cut = i; 699 700 col++; 701 i++; 702 } 703 704 if (col > maxcols && cut != SIZE_MAX) { 705 b->len = cut; 706 dbuf_printf(b, "\xe2\x80\xa6" /* U+2026 HORIZONTAL ELLIPSIS */); 707 } 708 } 709 710 static void 711 dbuf_flush(dbuf_t *b, FILE *out, size_t columns) 712 { 713 dbuf_truncate_tail(b, columns); 714 dbuf_style(b, NULL); 715 fwrite(b->buf, 1, b->len, out); 716 fputc('\n', out); 717 b->len = 0; 718 } 719 720 void 721 debug_highlight_print_disassembly(FILE *out, const char *function, 722 const debug_disasm_insn_t *insns, 723 size_t ninsns, size_t columns) 724 { 725 dbuf_t line = { 0 }; 726 static const style_t st_none = { FG_NONE, 0, 0 }; 727 static const style_t st_op = { FG_BMAGENT, 0, 0 }; 728 static const style_t st_cyan = { FG_CYAN, 0, 0 }; 729 static const style_t st_white = { FG_BWHITE, 0, 0 }; 730 static const style_t st_yellow = { FG_YELLOW, 0, 0 }; 731 static const style_t st_red = { FG_RED, 0, 0 }; 732 733 fprintf(out, "Function: %s\n", function ? function : "?"); 734 735 for (size_t idx = 0; idx < ninsns; idx++) { 736 const debug_disasm_insn_t *ins = &insns[idx]; 737 int fmt = ins->format; 738 int absfmt = (fmt < 0) ? -fmt : fmt; 739 740 if (absfmt > 4) 741 absfmt = 4; 742 743 dbuf_printf(&line, "%06zu:", ins->offset); 744 745 /* Only the base instruction (opcode + its fixed-width operand, per 746 * `format`) is shown here - CLFN/ARFN's per-upvalue-capture bytes 747 * and CALL's per-argument unpack bytes that may follow in `bytes` 748 * (uc_vm_insn_call() needs the *full* instruction length to skip 749 * over them) get their own indented hex dump below instead. */ 750 for (size_t j = 0; j < ins->nbytes && j <= (size_t)absfmt; j++) { 751 dbuf_printf(&line, " "); 752 dbuf_style(&line, (j == 0) ? &st_none : &st_op); 753 dbuf_printf(&line, "%02x", ins->bytes[j]); 754 dbuf_style(&line, NULL); 755 } 756 757 for (int j = 0; j < 3 * (4 - absfmt); j++) 758 dbuf_printf(&line, " "); 759 760 dbuf_printf(&line, " %7s", ins->mnemonic ? ins->mnemonic : "?"); 761 762 switch (fmt) { 763 case 0: 764 break; 765 766 case -4: { 767 int64_t v = ins->operand; 768 uint32_t mag = (uint32_t)((v < 0) ? -v : v); 769 770 dbuf_printf(&line, " {"); 771 dbuf_style(&line, &st_op); 772 dbuf_printf(&line, "%c0x%x", (v < 0) ? '-' : '+', mag); 773 dbuf_style(&line, NULL); 774 dbuf_printf(&line, "}"); 775 break; 776 } 777 778 case 1: 779 dbuf_printf(&line, " {"); 780 dbuf_style(&line, &st_op); 781 dbuf_printf(&line, "%" PRIu64, (uint64_t)ins->operand); 782 dbuf_style(&line, NULL); 783 dbuf_printf(&line, "}"); 784 break; 785 786 case 2: 787 dbuf_printf(&line, " {"); 788 dbuf_style(&line, &st_op); 789 dbuf_printf(&line, "0x%" PRIx64, (uint64_t)ins->operand); 790 dbuf_style(&line, NULL); 791 dbuf_printf(&line, "}"); 792 break; 793 794 case 4: 795 dbuf_printf(&line, " {"); 796 dbuf_style(&line, &st_op); 797 dbuf_printf(&line, "0x%" PRIx64, (uint64_t)ins->operand); 798 dbuf_style(&line, NULL); 799 800 if (ins->have_constant) { 801 dbuf_printf(&line, " : "); 802 dbuf_style(&line, ins->constant_is_string ? &st_op : &st_cyan); 803 dbuf_printf(&line, "%s", ins->constant_repr ? ins->constant_repr : "null"); 804 dbuf_style(&line, NULL); 805 } 806 else if (ins->variable_kind && strcmp(ins->variable_kind, "global") == 0) { 807 dbuf_printf(&line, " : global "); 808 dbuf_style(&line, &st_white); 809 dbuf_printf(&line, "%s", ins->variable_name ? ins->variable_name : "(unknown)"); 810 dbuf_style(&line, NULL); 811 } 812 else if (ins->variable_kind) { 813 bool upval = !strcmp(ins->variable_kind, "upval"); 814 815 dbuf_printf(&line, " : %s ", ins->variable_kind); 816 dbuf_style(&line, upval ? &st_cyan : &st_white); 817 dbuf_printf(&line, "%s", ins->variable_name ? ins->variable_name : "(unknown)"); 818 dbuf_style(&line, NULL); 819 } 820 else if (ins->have_closure) { 821 dbuf_printf(&line, " : %s ", ins->closure_kind ? ins->closure_kind : "closure"); 822 dbuf_style(&line, &st_op); 823 dbuf_printf(&line, "#%" PRIu32, ins->closure_index); 824 dbuf_style(&line, NULL); 825 } 826 else if (ins->have_call) { 827 dbuf_printf(&line, " : "); 828 829 if (ins->call_mcall) 830 dbuf_printf(&line, "mcall, "); 831 832 dbuf_style(&line, &st_op); 833 dbuf_printf(&line, "%" PRIu32, ins->call_nargs); 834 dbuf_style(&line, NULL); 835 dbuf_printf(&line, " arg%s", (ins->call_nargs == 1) ? "" : "s"); 836 837 if (ins->call_tail) { 838 dbuf_style(&line, &st_yellow); 839 dbuf_printf(&line, " (tail call)"); 840 dbuf_style(&line, NULL); 841 } 842 } 843 844 dbuf_printf(&line, "}"); 845 break; 846 847 default: 848 dbuf_style(&line, &st_red); 849 dbuf_printf(&line, " (unknown operand format: %d)", fmt); 850 dbuf_style(&line, NULL); 851 break; 852 } 853 854 /* this I_RETURN terminates a tail call: the 0x00 marker byte the 855 * compiler emits right after it was consumed as part of this 856 * instruction (it is pure data, never executed), so annotate the 857 * return rather than showing a separate NOOP line. */ 858 if (ins->return_tailcall) { 859 dbuf_printf(&line, " ; "); 860 dbuf_style(&line, &st_yellow); 861 dbuf_printf(&line, "tail call (marker consumed)"); 862 dbuf_style(&line, NULL); 863 } 864 865 dbuf_flush(&line, out, columns); 866 867 for (size_t j = 0; j < ins->ncaptures; j++) { 868 bool upval = ins->captures[j].upval; 869 int64_t slot = ins->captures[j].slot; 870 uint32_t mag = (uint32_t)((slot < 0) ? -slot : slot); 871 872 dbuf_printf(&line, " \xe2\x80\xa6 " /* " … " */); 873 dbuf_style(&line, &st_yellow); 874 875 for (size_t k = 0; k < 4; k++) 876 dbuf_printf(&line, "%s%02x", k ? " " : "", ins->captures[j].bytes[k]); 877 878 dbuf_style(&line, NULL); 879 dbuf_printf(&line, " capture {"); 880 dbuf_style(&line, &st_yellow); 881 dbuf_printf(&line, "%c0x%x", (slot < 0) ? '-' : '+', mag); 882 dbuf_style(&line, NULL); 883 dbuf_printf(&line, " : %s ", upval ? "upval" : "local"); 884 dbuf_style(&line, upval ? &st_cyan : &st_white); 885 dbuf_printf(&line, "%s", ins->captures[j].name ? ins->captures[j].name : "(unknown)"); 886 dbuf_style(&line, NULL); 887 dbuf_printf(&line, "}"); 888 889 dbuf_flush(&line, out, columns); 890 } 891 892 for (size_t j = 0; j < ins->nunpacks; j++) { 893 uint16_t slot = ins->unpacks[j].slot; 894 895 dbuf_printf(&line, " \xe2\x80\xa6 " /* " … " */); 896 dbuf_style(&line, &st_yellow); 897 dbuf_printf(&line, "%02x %02x", ins->unpacks[j].bytes[0], ins->unpacks[j].bytes[1]); 898 dbuf_style(&line, NULL); 899 dbuf_printf(&line, " unpack {"); 900 dbuf_style(&line, &st_yellow); 901 dbuf_printf(&line, "0x%x", slot); 902 dbuf_style(&line, NULL); 903 dbuf_printf(&line, " : stack slot "); 904 dbuf_style(&line, &st_op); 905 dbuf_printf(&line, "-0x%x", (unsigned)(slot + 1)); 906 dbuf_style(&line, NULL); 907 dbuf_printf(&line, "}"); 908 909 dbuf_flush(&line, out, columns); 910 } 911 } 912 913 free(line.buf); 914 } 915 916 /* -- variables listing, ported from print_variables() (formerly 917 * lib/debug.c) ------------------------------------------------------------ */ 918 919 /* Like dbuf_truncate_tail(), but for a compact JSON-ish value repr: places 920 * the ellipsis just before a synthetic closing bracket/quote so a truncated 921 * object/array/string still visually reads as one and stays on a single 922 * line, matching printbuf_append_uv()'s (formerly lib/debug.c) truncation 923 * exactly. Byte-based rather than UTF-8-aware, the same simplification 924 * truncate_head() above documents. */ 925 static void 926 dbuf_truncate_value(dbuf_t *b, size_t maxcols) 927 { 928 const char *end; 929 size_t keep; 930 931 if (maxcols == 0 || b->len <= maxcols) 932 return; 933 934 switch (b->buf[0]) { 935 case '{': keep = (maxcols > 3) ? maxcols - 3 : 0; end = "\xe2\x80\xa6 }"; break; 936 case '[': keep = (maxcols > 3) ? maxcols - 3 : 0; end = "\xe2\x80\xa6 ]"; break; 937 case '"': keep = (maxcols > 2) ? maxcols - 2 : 0; end = "\xe2\x80\xa6\""; break; 938 default: keep = (maxcols > 1) ? maxcols - 1 : 0; end = "\xe2\x80\xa6"; break; 939 } 940 941 b->len = keep; 942 dbuf_printf(b, "%s", end); 943 } 944 945 void 946 debug_highlight_print_variables(FILE *out, const debug_variable_t *vars, 947 size_t nvars, const char *indent, 948 size_t columns) 949 { 950 static const style_t st_upval = { FG_CYAN, 0, BOLD }; 951 static const style_t st_faint = { FG_BWHITE, 0, FAINT }; 952 static const style_t st_err = { FG_RED, 0, BOLD }; 953 static const char shadowed_suffix[] = " (shadowed)"; 954 size_t indent_len = indent ? strlen(indent) : 0; 955 size_t value_cols = 0; 956 dbuf_t namebuf = { 0 }, valuebuf = { 0 }; 957 958 if (columns > indent_len + 19) 959 value_cols = columns - indent_len - 19; 960 961 for (size_t i = 0; i < nvars; i++) { 962 const debug_variable_t *v = &vars[i]; 963 const char *kind = v->kind ? v->kind : ""; 964 const char *name = v->name ? v->name : "?"; 965 const char *repr = v->value_repr ? v->value_repr : ""; 966 bool upval = !strcmp(kind, "upvalue"); 967 bool faint = v->shadowed || !strcmp(kind, "this") || !strcmp(kind, "internal"); 968 bool err = !strcmp(repr, "<out of range>"); 969 size_t namelen; 970 971 namebuf.len = 0; 972 valuebuf.len = 0; 973 974 dbuf_printf(&namebuf, "%s", name); 975 namelen = namebuf.len; 976 dbuf_truncate_tail(&namebuf, 16); 977 978 if (indent) 979 fputs(indent, out); 980 981 /* A shadowed entry is rendered faint throughout, taking priority 982 * over its own kind's usual color (still cyan/upvalue matters 983 * far less than "this isn't what the name resolves to anymore"). */ 984 if (v->shadowed) 985 cs(out, &st_faint); 986 else if (upval) 987 cs(out, &st_upval); 988 else if (faint) 989 cs(out, &st_faint); 990 991 fwrite(namebuf.buf, 1, namebuf.len, out); 992 993 if (v->shadowed || upval || faint) 994 cs(out, NULL); 995 996 for (; namelen < 16; namelen++) 997 fputc(' ', out); 998 999 cs(out, &st_faint); 1000 fputs(" : ", out); 1001 cs(out, NULL); 1002 1003 if (err) { 1004 cs(out, &st_err); 1005 fputs(repr, out); 1006 cs(out, NULL); 1007 } 1008 else { 1009 size_t this_value_cols = value_cols; 1010 1011 /* Reserve room for the trailing "(shadowed)" marker printed 1012 * below, or it doesn't count against the line's width budget 1013 * and can push the whole line past `columns`, wrapping. */ 1014 if (v->shadowed && this_value_cols > sizeof(shadowed_suffix) - 1) 1015 this_value_cols -= sizeof(shadowed_suffix) - 1; 1016 1017 if (v->shadowed) 1018 cs(out, &st_faint); 1019 1020 dbuf_printf(&valuebuf, "%s", repr); 1021 1022 /* value_repr is always the compact, single-line repr (see 1023 * build_variables_json() in lib/debug.c) - guard against a 1024 * literal embedded newline anyway, since byte-counting 1025 * truncation across one would garble rather than shorten it. */ 1026 if (columns > 0 && !strchr(repr, '\n')) 1027 dbuf_truncate_value(&valuebuf, this_value_cols); 1028 1029 fwrite(valuebuf.buf, 1, valuebuf.len, out); 1030 1031 if (v->shadowed) 1032 cs(out, NULL); 1033 } 1034 1035 if (v->shadowed) { 1036 cs(out, &st_faint); 1037 fputs(shadowed_suffix, out); 1038 cs(out, NULL); 1039 } 1040 1041 fputc('\n', out); 1042 } 1043 1044 free(namebuf.buf); 1045 free(valuebuf.buf); 1046 } 1047
This page was automatically generated by LXR 0.3.1. • OpenWrt