• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nickg / nvc / 22829821688

08 Mar 2026 09:02PM UTC coverage: 92.57% (-0.02%) from 92.594%
22829821688

push

github

nickg
Do not silently drop backslashes in string literals

1 of 1 new or added line in 1 file covered. (100.0%)

831 existing lines in 12 files now uncovered.

76744 of 82904 relevant lines covered (92.57%)

445442.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.21
/src/printf.c
1
//
2
//  Copyright (C) 2025  Nick Gasson
3
//
4
//  This program is free software: you can redistribute it and/or modify
5
//  it under the terms of the GNU General Public License as published by
6
//  the Free Software Foundation, either version 3 of the License, or
7
//  (at your option) any later version.
8
//
9
//  This program is distributed in the hope that it will be useful,
10
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
//  GNU General Public License for more details.
13
//
14
//  You should have received a copy of the GNU General Public License
15
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

18
#include "util.h"
19
#include "ident.h"
20
#include "printf.h"
21
#include "thread.h"
22
#include "type.h"
23

24
#include <assert.h>
25
#include <stdio.h>
26
#include <string.h>
27
#include <stdlib.h>
28
#include <unistd.h>
29
#include <limits.h>
30

31
#define ANSI_RESET      0
32
#define ANSI_BOLD       1
33
#define ANSI_FG_BLACK   30
34
#define ANSI_FG_RED     31
35
#define ANSI_FG_GREEN   32
36
#define ANSI_FG_YELLOW  33
37
#define ANSI_FG_BLUE    34
38
#define ANSI_FG_MAGENTA 35
39
#define ANSI_FG_CYAN    36
40
#define ANSI_FG_WHITE   37
41

42
#define MAX_ARGS 25
43

44
typedef struct _printf_state printf_state_t;
45
typedef struct _printf_arg printf_arg_t;
46

47
typedef union {
48
   long long  ll;
49
   long       l;
50
   size_t     z;
51
   double     f;
52
   int        i;
53
   void      *p;
54
} printf_value_t;
55

56
typedef int (*fmt_fn_t)(ostream_t *, printf_state_t *, printf_arg_t *);
57

58
typedef struct _printf_arg {
59
   const char     *start;
60
   size_t          len;
61
   fmt_fn_t        fn;
62
   printf_value_t  value;
63
   int             precision;
64
} printf_arg_t;
65

66
typedef struct _printf_state {
67
   printf_arg_t args[MAX_ARGS];
68
   unsigned     nargs;
69
   unsigned     pos;
70
} printf_state_t;
71

72
typedef struct {
73
   const char *name;
74
   int         value;
75
} color_escape_t;
76

77
static const color_escape_t escapes[] = {
78
   { "",        ANSI_RESET },
79
   { "bold",    ANSI_BOLD },
80
   { "black",   ANSI_FG_BLACK },
81
   { "red",     ANSI_FG_RED },
82
   { "green",   ANSI_FG_GREEN },
83
   { "yellow",  ANSI_FG_YELLOW },
84
   { "blue",    ANSI_FG_BLUE },
85
   { "magenta", ANSI_FG_MAGENTA },
86
   { "cyan",    ANSI_FG_CYAN },
87
   { "white",   ANSI_FG_WHITE },
88
};
89

90
static int printf_interpret(ostream_t *os, printf_state_t *state,
91
                            const char *fmt, const char *end);
92

93
int ostream_write(ostream_t *os, const char *buf, size_t len)
1,697,313✔
94
{
95
   (*os->callback)(buf, len, os->context);
1,697,313✔
96
   return len;
1,697,313✔
97
}
98

99
int ostream_putc(ostream_t *os, char ch)
600,317✔
100
{
101
   char buf[1] = { ch };
600,317✔
102
   return ostream_write(os, buf, 1);
600,317✔
103
}
104

105
int ostream_puts(ostream_t *os, const char *str)
8,781✔
106
{
107
   return ostream_write(os, str, strlen(str));
8,781✔
108
}
109

110
static int format_ident(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
41✔
111
{
112
   ident_t id = arg->value.p;
41✔
113
   return ostream_write(os, istr(id), ident_len(id));
41✔
114
}
115

116
static int format_ident_toupper(ostream_t *os, printf_state_t *state,
172✔
117
                                printf_arg_t *arg)
118
{
119
   ident_t id = arg->value.p;
172✔
120
   size_t len = ident_len(id);
172✔
121
   const char *str = istr(id);
172✔
122
   int nchars = 0;
172✔
123
   char chunk[32];
172✔
124

125
   for (int i = 0; i < len; i += sizeof(chunk)) {
344✔
126
      const int tocopy = MIN(sizeof(chunk), len - i);
172✔
127
      for (int j = 0; j < tocopy; j++)
702✔
128
         chunk[j] = toupper_iso88591(str[i + j]);
530✔
129

130
      nchars += ostream_write(os, chunk, tocopy);
172✔
131
   }
132

133
   return nchars;
172✔
134
}
135

136
static int format_type(ostream_t *os, printf_state_t *state, printf_arg_t *arg)
208✔
137
{
138
   type_t type = arg->value.p;
208✔
139

140
   // Print a fully qualified name if there is another type in the
141
   // argument list with the same simple name
142

143
   for (int i = 0; i < state->nargs; i++) {
396✔
144
      const printf_arg_t *other = &(state->args[i]);
322✔
145
      if (other != arg && other->fn == format_type)
322✔
146
         return ostream_puts(os, type_pp2(type, other->value.p));
134✔
147
   }
148

149
   return ostream_puts(os, type_pp(type));
74✔
150
}
151

152
static int delegate(ostream_t *os, printf_state_t *s, printf_arg_t *arg, ...)
556,227✔
153
{
154
   char spec[32];
556,227✔
155
   assert(arg->len + 1 < sizeof(spec));
556,227✔
156
   memcpy(spec, arg->start, arg->len);
556,227✔
157
   spec[arg->len] = '\0';
556,227✔
158

159
   va_list ap, ap2;
556,227✔
160
   va_start(ap, arg);
556,227✔
161
   va_copy(ap2, ap);
556,227✔
162

163
   char small[64];
556,227✔
164
   int req = vsnprintf(small, sizeof(small), spec, ap);
556,227✔
165

166
   if (req + 1 > sizeof(small)) {
556,227✔
167
      char *large = xmalloc(req + 1);
650✔
168
      vsnprintf(large, req + 1, spec, ap2);
650✔
169
      ostream_write(os, large, req);
650✔
170
      free(large);
650✔
171
   }
172
   else
173
      ostream_write(os, small, req);
555,577✔
174

175
   va_end(ap);
556,227✔
176
   va_end(ap2);
556,227✔
177
   return req;
556,227✔
178
}
179

180
static fmt_fn_t get_pointer_formatter(char ch)
434✔
181
{
182
   switch (ch) {
434✔
183
   case 'i': return format_ident;
184
   case 'I': return format_ident_toupper;
172✔
185
   case 'T': return format_type;
208✔
186
   default: return NULL;
13✔
187
   }
188
}
189

190
static int ansi_escape(ostream_t *os, printf_state_t *state, const char **fmt)
153,547✔
191
{
192
   bool has_format = false;
153,547✔
193
   const char *start = *fmt;
153,547✔
194
   do {
805,374✔
195
      has_format |= (**fmt == '%');
805,374✔
196
      (*fmt)++;
805,374✔
197
   } while (**fmt != '\0' && **fmt != '$');
805,374✔
198

199
   if (**fmt == '\0')
153,547✔
200
      return ostream_write(os, start, *fmt - start);
73✔
201

202
   const char *end = *fmt;
153,474✔
203
   (*fmt)++;   // Advance past final '$'
153,474✔
204

205
   size_t len = len = *fmt - start - 2;
153,474✔
206
   const char *e = e = start + 1;
153,474✔
207
   LOCAL_TEXT_BUF tb = NULL;
306,948✔
208

209
   if (has_format) {
153,474✔
210
      // Expand any embedded formatting inside the ANSI escape
211
      tb = tb_new();
16,497✔
212

213
      ostream_t aos = {
16,497✔
214
         tb_ostream_write,
215
         tb,
216
         os->charset,
16,497✔
217
      };
218

219
      printf_interpret(&aos, state, start + 1, end);
16,497✔
220

221
      e = tb_get(tb);
16,497✔
222
      len = tb_len(tb);
16,497✔
223
   }
224

225
   bool bold;
153,474✔
226
   if ((bold = (*e == '!')))
153,474✔
227
      ++e, --len;
5✔
228

229
   bool bright;
153,474✔
230
   if ((bright = (*e == '+')))
153,474✔
231
      ++e, --len;
29✔
232

233
   if (*e == '#') {
153,474✔
234
      char *eptr;
1✔
235
      int code = strtoul(e + 1, &eptr, 10);
1✔
236
      if (eptr == e + len) {
1✔
237
         char buf[16];
1✔
238
         if (bold)
1✔
UNCOV
239
            checked_sprintf(buf, sizeof(buf), "\033[1;38;5;%dm", code);
×
240
         else
241
            checked_sprintf(buf, sizeof(buf), "\033[38;5;%dm", code);
1✔
242

243
         if (os->flags & OS_COLOR)
1✔
244
            ostream_puts(os, buf);
1✔
245

246
         return 0;
1✔
247
      }
248
   }
249

250
   if (strncmp(e, "link:", 5) == 0) {
153,473✔
251
      const char *bel = strchr(e, '\07');
16,498✔
252

253
#ifndef __MINGW32__    // Winpty doesn't recognise these
254
      if (os->flags & OS_TERMINAL) {
16,498✔
255
         ostream_puts(os, "\033]8;;");
2✔
256
         ostream_write(os, e + 5, len - 5);
2✔
257
         ostream_puts(os, "\033]8;;\07");
2✔
258
         return e + len - bel - 1;
2✔
259
      }
260
#endif
261

262
      return ostream_write(os, bel + 1, e + len - bel - 1);
16,496✔
263
   }
264

265
   for (int i = 0; i < ARRAY_LEN(escapes); i++) {
518,356✔
266
      if (strncmp(e, escapes[i].name, len) == 0) {
518,354✔
267
         int code = escapes[i].value + (bright ? 60 : 0);
136,973✔
268
         char buf[16];
136,973✔
269
         if (bold)
136,973✔
270
            checked_sprintf(buf, sizeof(buf), "\033[1;%dm", code);
4✔
271
         else
272
            checked_sprintf(buf, sizeof(buf), "\033[%dm", code);
136,969✔
273

274
         if (os->flags & OS_COLOR)
136,973✔
275
            ostream_puts(os, buf);
1,380✔
276

277
         return 0;
136,973✔
278
      }
279
   }
280

281
   return ostream_write(os, start, *fmt - start);
2✔
282
}
283

284
static int format_z(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
122,487✔
285
{
286
   assert(arg->precision == INT_MIN);
122,487✔
287
   return delegate(os, s, arg, arg->value.z);
122,487✔
288
}
289

UNCOV
290
static int format_ll(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
×
291
{
UNCOV
292
   if (arg->precision != INT_MIN)
×
293
      return delegate(os, s, arg, arg->precision, arg->value.ll);
×
294
   else
UNCOV
295
      return delegate(os, s, arg, arg->value.ll);
×
296
}
297

298
static int format_l(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
30,617✔
299
{
300
   if (arg->precision != INT_MIN)
30,617✔
301
      return delegate(os, s, arg, arg->precision, arg->value.l);
6,226✔
302
   else
303
      return delegate(os, s, arg, arg->value.l);
24,391✔
304
}
305

306
static int format_i(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
66,878✔
307
{
308
   if (arg->precision != INT_MIN)
66,878✔
309
      return delegate(os, s, arg, arg->precision, arg->value.i);
10,234✔
310
   else
311
      return delegate(os, s, arg, arg->value.i);
56,644✔
312
}
313

314
static int format_f(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
656✔
315
{
316
   if (arg->precision != INT_MIN)
656✔
317
      return delegate(os, s, arg, arg->precision, arg->value.f);
7✔
318
   else
319
      return delegate(os, s, arg, arg->value.f);
649✔
320
}
321

322
static int format_s(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
335,576✔
323
{
324
   if (arg->precision != INT_MIN)
335,576✔
325
      return delegate(os, s, arg, arg->precision, arg->value.p);
41,593✔
326
   else
327
      return delegate(os, s, arg, arg->value.p);
293,983✔
328
}
329

330
static int format_p(ostream_t *os, printf_state_t *s, printf_arg_t *arg)
13✔
331
{
332
   return delegate(os, s, arg, arg->value.p);
13✔
333
}
334

335
static int printf_interpret(ostream_t *os, printf_state_t *state,
464,006✔
336
                            const char *fmt, const char *end)
337
{
338
   int nchars = 0;
464,006✔
339
   for (const char *p = fmt;;) {
464,006✔
340
      const char *start = p;
1,182,041✔
341
      while (p < end && *p != '%' && *p != '$')
2,770,698✔
342
         p++;
1,588,657✔
343

344
      if (start < p)
1,182,041✔
345
         nchars += ostream_write(os, start, p - start);
484,776✔
346

347
      if (p == end)
1,182,041✔
348
         return nchars;
464,006✔
349
      else if (*p == '$') {
718,035✔
350
         nchars += ansi_escape(os, state, &p);
153,547✔
351
         continue;
153,547✔
352
      }
353
      else if (*p == '%' && *(p + 1) == '%') {
564,488✔
354
         nchars += ostream_write(os, "%", 1);
7,840✔
355
         p += 2;
7,840✔
356
         continue;
7,840✔
357
      }
358

359
      assert(state->pos < state->nargs);
556,648✔
360

361
      printf_arg_t *arg = &(state->args[state->pos]);
556,648✔
362
      nchars += (*arg->fn)(os, state, arg);
556,648✔
363
      p += arg->len;
556,648✔
364

365
      state->pos++;
556,648✔
366
   }
367
}
368

369
int nvc_vfprintf(ostream_t *os, const char *fmt, va_list ap)
447,509✔
370
{
371
   printf_state_t state = {};
447,509✔
372

373
   const char *end = NULL;
447,509✔
374
   for (const char *p = fmt;;) {
447,509✔
375
      while (*p != '\0' && *p != '%')
3,180,084✔
376
         p++;
2,168,087✔
377

378
      if (*p == '\0') {
1,011,997✔
379
         end = p;
447,509✔
380
         break;
447,509✔
381
      }
382

383
      printf_arg_t arg = { .start = p, .precision = INT_MIN };
564,488✔
384
      bool z_mod = false;
564,488✔
385
      int l_mod = 0;
564,488✔
386
   again:
794,100✔
387
      switch (*++p) {
794,100✔
388
      case 'l':
30,619✔
389
         l_mod++;
30,619✔
390
         goto again;
30,619✔
391
      case 'z':
122,487✔
392
         z_mod = true;
122,487✔
393
         goto again;
122,487✔
394
      case '-':
18,446✔
395
      case '+':
396
      case '.':
397
      case '0'...'9':
398
         goto again;
18,446✔
399
      case '*':
58,060✔
400
         arg.precision = va_arg(ap, int);
58,060✔
401
         goto again;
58,060✔
402
      case 'd':
218,924✔
403
      case 'i':
404
      case 'x':
405
      case 'u':
406
         if (z_mod) {
218,924✔
407
            arg.value.z = va_arg(ap, size_t);
122,487✔
408
            arg.fn = format_z;
122,487✔
409
         }
410
         else if (l_mod >= 2) {
96,437✔
UNCOV
411
            arg.value.ll = va_arg(ap, long long);
×
412
            arg.fn = format_ll;
×
413
         }
414
         else if (l_mod == 1) {
96,437✔
415
            arg.value.l = va_arg(ap, long);
30,617✔
416
            arg.fn = format_l;
30,617✔
417
         }
418
         else {
419
            arg.value.i = va_arg(ap, int);
65,820✔
420
            arg.fn = format_i;
65,820✔
421
         }
422
         break;
423
      case 'c':
1,058✔
424
         arg.value.i = va_arg(ap, int);
1,058✔
425
         arg.fn = format_i;
1,058✔
426
         break;
1,058✔
427
      case 'e':
656✔
428
      case 'f':
429
      case 'g':
430
         arg.value.f = va_arg(ap, double);
656✔
431
         arg.fn = format_f;
656✔
432
         break;
656✔
433
      case 's':
335,576✔
434
         arg.value.p = va_arg(ap, char *);
335,576✔
435
         arg.fn = format_s;
335,576✔
436
         break;
335,576✔
437
      case 'p':
434✔
438
         arg.value.p = va_arg(ap, void *);
434✔
439
         if ((arg.fn = get_pointer_formatter(p[1])))
434✔
440
            p++;
421✔
441
         else
442
            arg.fn = format_p;
443
         break;
444
      case '%':
7,840✔
445
         p++;
7,840✔
446
         continue;
7,840✔
UNCOV
447
      default:
×
448
         fatal_trace("unhandled character '%c' in format", *p);
449
      }
450

451
      arg.len = ++p - arg.start;
556,648✔
452

453
      if (state.nargs == MAX_ARGS)
556,648✔
454
         fatal_trace("maximum of %d printf arguments supported", MAX_ARGS);
455
      else
456
         state.args[state.nargs++] = arg;
556,648✔
457
   }
458

459
   return printf_interpret(os, &state, fmt, end);
895,018✔
460
}
461

462
int nvc_vprintf(const char *fmt, va_list ap)
10✔
463
{
464
   return nvc_vfprintf(nvc_stdout(), fmt, ap);
10✔
465
}
466

467
int nvc_printf(const char *fmt, ...)
10✔
468
{
469
   va_list ap;
10✔
470
   va_start(ap, fmt);
10✔
471
   const int nchars = nvc_vprintf(fmt, ap);
10✔
472
   va_end(ap);
10✔
473
   return nchars;
10✔
474
}
475

476
int nvc_fprintf(ostream_t *os, const char *fmt, ...)
128,849✔
477
{
478
   va_list ap;
128,849✔
479
   va_start(ap, fmt);
128,849✔
480
   const int nchars = nvc_vfprintf(os, fmt, ap);
128,849✔
481
   va_end(ap);
128,849✔
482
   return nchars;
128,849✔
483
}
484

485
void stdio_ostream_write(const char *buf, size_t len, void *ctx)
873,805✔
486
{
487
   FILE *f = ctx;
873,805✔
488
   fwrite(buf, len, 1, f);
873,805✔
489
}
873,805✔
490

491
static void init_terminal_ostream(ostream_t *os, FILE *f)
3,120✔
492
{
493
   os->callback = stdio_ostream_write;
3,120✔
494
   os->context = f;
3,120✔
495
   os->flags = 0;
3,120✔
496

497
   if (isatty(fileno(f))) {
3,120✔
498
      os->charset = utf8_terminal() ? CHARSET_UTF8 : CHARSET_ISO88591;
×
499
      os->flags |= OS_TERMINAL;
×
500
   }
501
   else
502
      os->charset = CHARSET_ISO88591;
3,120✔
503

504
   if (color_terminal())
3,120✔
505
      os->flags |= OS_COLOR;
3✔
506
}
3,120✔
507

508
ostream_t *nvc_stdout(void)
174✔
509
{
510
   static ostream_t os;
174✔
511
   INIT_ONCE(init_terminal_ostream(&os, stdout));
174✔
512
   return &os;
174✔
513
}
514

515
ostream_t *nvc_stderr(void)
47,023✔
516
{
517
   static ostream_t os;
47,023✔
518
   INIT_ONCE(init_terminal_ostream(&os, stderr));
47,023✔
519
   return &os;
47,023✔
520
}
521

522
char *color_asprintf(const char *fmt, ...)
23✔
523
{
524
   LOCAL_TEXT_BUF tb = tb_new();
46✔
525
   ostream_t os = { tb_ostream_write, tb, CHARSET_ISO88591, OS_COLOR };
23✔
526

527
   va_list ap;
23✔
528
   va_start(ap, fmt);
23✔
529
   nvc_vfprintf(&os, fmt, ap);
23✔
530
   va_end(ap);
23✔
531

532
   return tb_claim(tb);
23✔
533
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc