• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/debug_highlight.c

  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