- first version of scanf parameter check
[cparser] / format_check.c
1 /*
2  * This file is part of cparser.
3  * Copyright (C) 2007-2008 Matthias Braun <matze@braunis.de>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18  * 02111-1307, USA.
19  */
20 #include <ctype.h>
21 #include <wctype.h>
22
23 #include "format_check.h"
24 #include "symbol_t.h"
25 #include "ast_t.h"
26 #include "entity_t.h"
27 #include "diagnostic.h"
28 #include "types.h"
29 #include "type_t.h"
30 #include "warning.h"
31 #include "lang_features.h"
32
33 typedef enum format_flag_t {
34         FMT_FLAG_NONE  = 0,
35         FMT_FLAG_HASH  = 1U << 0,
36         FMT_FLAG_ZERO  = 1U << 1,
37         FMT_FLAG_MINUS = 1U << 2,
38         FMT_FLAG_SPACE = 1U << 3,
39         FMT_FLAG_PLUS  = 1U << 4,
40         FMT_FLAG_TICK  = 1U << 5
41 } format_flag_t;
42
43 typedef unsigned format_flags_t;
44
45 typedef enum format_length_modifier_t {
46         FMT_MOD_NONE,
47         FMT_MOD_L,
48         FMT_MOD_hh,
49         FMT_MOD_h,
50         FMT_MOD_l,
51         FMT_MOD_ll,
52         FMT_MOD_j,
53         FMT_MOD_t,
54         FMT_MOD_z,
55         FMT_MOD_q,
56         /* only in microsoft mode */
57         FMT_MOD_w,
58         FMT_MOD_I,
59         FMT_MOD_I32,
60         FMT_MOD_I64
61 } format_length_modifier_t;
62
63 typedef struct format_spec_t {
64         const char    *name;     /**< name of the function */
65         format_kind_t  fmt_kind; /**< kind */
66         unsigned       fmt_idx;  /**< index of the format string */
67         unsigned       arg_idx;  /**< index of the first argument */
68 } format_spec_t;
69
70 static const char* get_length_modifier_name(const format_length_modifier_t mod)
71 {
72         static const char* const names[] = {
73                 [FMT_MOD_NONE] = "",
74                 [FMT_MOD_L]    = "L",
75                 [FMT_MOD_hh]   = "hh",
76                 [FMT_MOD_h]    = "h",
77                 [FMT_MOD_l]    = "l",
78                 [FMT_MOD_ll]   = "ll",
79                 [FMT_MOD_j]    = "j",
80                 [FMT_MOD_t]    = "t",
81                 [FMT_MOD_z]    = "z",
82                 [FMT_MOD_q]    = "q",
83                 /* only in microsoft mode */
84                 [FMT_MOD_w]    = "w",
85                 [FMT_MOD_I]    = "I",
86                 [FMT_MOD_I32]  = "I32",
87                 [FMT_MOD_I64]  = "I64"
88         };
89         assert(mod < sizeof(names) / sizeof(*names));
90         return names[mod];
91 }
92
93 static void warn_invalid_length_modifier(const source_position_t *pos,
94                                          const format_length_modifier_t mod,
95                                          const wchar_rep_t conversion)
96 {
97         warningf(pos,
98                 "invalid length modifier '%s' for conversion specifier '%%%c'",
99                 get_length_modifier_name(mod), conversion
100         );
101 }
102
103 typedef struct vchar_t vchar_t;
104 struct vchar_t {
105         const void *string;   /**< the string */
106         size_t     position;  /**< current position */
107         size_t     size;      /**< size of the string */
108
109         /** return the first character of the string and setthe position to 0. */
110         unsigned (*first)(vchar_t *self);
111         /** return the next character of the string */
112         unsigned (*next)(vchar_t *self);
113         /** return non_zero if the given character is a digit */
114         int (*is_digit)(unsigned vchar);
115 };
116
117 static unsigned string_first(vchar_t *self) {
118         self->position = 0;
119         const string_t *string = self->string;
120         return string->begin[0];
121 }
122
123 static unsigned string_next(vchar_t *self) {
124         ++self->position;
125         const string_t *string = self->string;
126         return string->begin[self->position];
127 }
128
129 static int string_isdigit(unsigned vchar) {
130         return isdigit(vchar);
131 }
132
133 static unsigned wstring_first(vchar_t *self) {
134         self->position = 0;
135         const wide_string_t *wstring = self->string;
136         return wstring->begin[0];
137 }
138
139 static unsigned wstring_next(vchar_t *self) {
140         ++self->position;
141         const wide_string_t *wstring = self->string;
142         return wstring->begin[self->position];
143 }
144
145 static int wstring_isdigit(unsigned vchar) {
146         return iswdigit(vchar);
147 }
148
149 static bool atend(vchar_t *self) {
150         return self->position + 1 == self->size;
151 }
152
153 /**
154  * Check printf-style format.
155  */
156 static void check_printf_format(const call_argument_t *arg, const format_spec_t *spec)
157 {
158         /* find format arg */
159         unsigned idx = 0;
160         for (; idx < spec->fmt_idx; ++idx)
161                 arg = arg->next;
162
163         const expression_t *fmt_expr = arg->expression;
164         if (fmt_expr->kind == EXPR_UNARY_CAST_IMPLICIT) {
165                 fmt_expr = fmt_expr->unary.value;
166         }
167
168         vchar_t vchar;
169         if (fmt_expr->kind == EXPR_WIDE_STRING_LITERAL) {
170                 vchar.string   = &fmt_expr->wide_string.value;
171                 vchar.size     = fmt_expr->wide_string.value.size;
172                 vchar.first    = wstring_first;
173                 vchar.next     = wstring_next;
174                 vchar.is_digit = wstring_isdigit;
175         } else if (fmt_expr->kind == EXPR_STRING_LITERAL) {
176                 vchar.string   = &fmt_expr->string.value;
177                 vchar.size     = fmt_expr->string.value.size;
178                 vchar.first    = string_first;
179                 vchar.next     = string_next;
180                 vchar.is_digit = string_isdigit;
181         } else {
182                 return;
183         }
184         /* find the real args */
185         for(; idx < spec->arg_idx; ++idx)
186                 arg = arg->next;
187
188         const source_position_t *pos = &fmt_expr->base.source_position;
189         unsigned fmt     = vchar.first(&vchar);
190         unsigned num_fmt = 0;
191         for (; fmt != '\0'; fmt = vchar.next(&vchar)) {
192                 if (fmt != '%')
193                         continue;
194                 fmt = vchar.next(&vchar);
195
196                 if (fmt == '%')
197                         continue;
198
199                 ++num_fmt;
200
201                 format_flags_t fmt_flags = FMT_FLAG_NONE;
202                 if (fmt == '0') {
203                         fmt = vchar.next(&vchar);
204                         fmt_flags |= FMT_FLAG_ZERO;
205                 }
206
207                 /* argument selector or minimum field width */
208                 if (vchar.is_digit(fmt)) {
209                         do {
210                                 fmt = vchar.next(&vchar);
211                         } while (vchar.is_digit(fmt));
212
213                         /* digit string was ... */
214                         if (fmt == '$') {
215                                 /* ... argument selector */
216                                 fmt_flags = FMT_FLAG_NONE; /* reset possibly set 0-flag */
217                                 /* TODO implement */
218                                 return;
219                         }
220                         /* ... minimum field width */
221                 } else {
222                         /* flags */
223                         for (;;) {
224                                 format_flags_t flag;
225                                 switch (fmt) {
226                                         case '#':  flag = FMT_FLAG_HASH;  break;
227                                         case '0':  flag = FMT_FLAG_ZERO;  break;
228                                         case '-':  flag = FMT_FLAG_MINUS; break;
229                                         case '\'': flag = FMT_FLAG_TICK;  break;
230
231                                         case ' ':
232                                                 if (fmt_flags & FMT_FLAG_PLUS) {
233                                                         warningf(pos, "' ' is overridden by prior '+' in conversion specification %u", num_fmt);
234                                                 }
235                                                 flag = FMT_FLAG_SPACE;
236                                                 break;
237
238                                         case '+':
239                                                 if (fmt_flags & FMT_FLAG_SPACE) {
240                                                         warningf(pos, "'+' overrides prior ' ' in conversion specification %u", num_fmt);
241                                                 }
242                                                 flag = FMT_FLAG_PLUS;
243                                                 break;
244
245                                         default: goto break_fmt_flags;
246                                 }
247                                 if (fmt_flags & flag) {
248                                         warningf(pos, "repeated flag '%c' in conversion specification %u", (char)fmt, num_fmt);
249                                 }
250                                 fmt_flags |= flag;
251                                 fmt = vchar.next(&vchar);
252                         }
253 break_fmt_flags:
254
255                         /* minimum field width */
256                         if (fmt == '*') {
257                                 fmt = vchar.next(&vchar);
258                                 if (arg == NULL) {
259                                         warningf(pos, "missing argument for '*' field width in conversion specification %u", num_fmt);
260                                         return;
261                                 }
262                                 const type_t *const arg_type = arg->expression->base.type;
263                                 if (arg_type != type_int) {
264                                         warningf(pos, "argument for '*' field width in conversion specification %u is not an 'int', but an '%T'", num_fmt, arg_type);
265                                 }
266                                 arg = arg->next;
267                         } else {
268                                 while (vchar.is_digit(fmt)) {
269                                         fmt = vchar.next(&vchar);
270                                 }
271                         }
272                 }
273
274                 /* precision */
275                 if (fmt == '.') {
276                         fmt = vchar.next(&vchar);
277                         if (fmt == '*') {
278                                 fmt = vchar.next(&vchar);
279                                 if (arg == NULL) {
280                                         warningf(pos, "missing argument for '*' precision in conversion specification %u", num_fmt);
281                                         return;
282                                 }
283                                 const type_t *const arg_type = arg->expression->base.type;
284                                 if (arg_type != type_int) {
285                                         warningf(pos, "argument for '*' precision in conversion specification %u is not an 'int', but an '%T'", num_fmt, arg_type);
286                                 }
287                                 arg = arg->next;
288                         } else {
289                                 /* digit string may be omitted */
290                                 while (vchar.is_digit(fmt)) {
291                                         fmt = vchar.next(&vchar);
292                                 }
293                         }
294                 }
295
296                 /* length modifier */
297                 format_length_modifier_t fmt_mod;
298                 switch (fmt) {
299                         case 'h':
300                                 fmt = vchar.next(&vchar);
301                                 if (fmt == 'h') {
302                                         fmt = vchar.next(&vchar);
303                                         fmt_mod = FMT_MOD_hh;
304                                 } else {
305                                         fmt_mod = FMT_MOD_h;
306                                 }
307                                 break;
308
309                         case 'l':
310                                 fmt = vchar.next(&vchar);
311                                 if (fmt == 'l') {
312                                         fmt = vchar.next(&vchar);
313                                         fmt_mod = FMT_MOD_ll;
314                                 } else {
315                                         fmt_mod = FMT_MOD_l;
316                                 }
317                                 break;
318
319                         case 'L': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_L;    break;
320                         case 'j': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_j;    break;
321                         case 't': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_t;    break;
322                         case 'z': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_z;    break;
323                         case 'q': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_q;    break;
324                         /* microsoft mode */
325                         case 'w':
326                                 if (c_mode & _MS) {
327                                         fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_w;
328                                 } else {
329                                         fmt_mod = FMT_MOD_NONE;
330                                 }
331                                 break;
332                         case 'I':
333                                 if (c_mode & _MS) {
334                                         fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_I;
335                                         if (fmt == '3') {
336                                                 fmt = vchar.next(&vchar);
337                                                 if (fmt == '2') {
338                                                         fmt = vchar.next(&vchar);
339                                                         fmt_mod = FMT_MOD_I32;
340                                                 } else {
341                                                         /* rewind */
342                                                         --vchar.position;
343                                                 }
344                                         } else if (fmt == '6') {
345                                                 fmt = vchar.next(&vchar);
346                                                 if (fmt == '4') {
347                                                         fmt = vchar.next(&vchar);
348                                                         fmt_mod = FMT_MOD_I64;
349                                                 } else {
350                                                         /* rewind */
351                                                         --vchar.position;
352                                                 }
353                                         }
354                                 } else {
355                                         fmt_mod = FMT_MOD_NONE;
356                                 }
357                                 break;
358                         default:
359                                 fmt_mod = FMT_MOD_NONE;
360                                 break;
361                 }
362
363                 if (fmt == '\0') {
364                         warningf(pos, "dangling %% in format string");
365                         break;
366                 }
367
368                 type_t            *expected_type;
369                 type_qualifiers_t  expected_qual = TYPE_QUALIFIER_NONE;
370                 format_flags_t     allowed_flags;
371                 switch (fmt) {
372                         case 'd':
373                         case 'i':
374                                 switch (fmt_mod) {
375                                         case FMT_MOD_NONE: expected_type = type_int;       break;
376                                         case FMT_MOD_hh:   expected_type = type_int;       break; /* TODO promoted signed char */
377                                         case FMT_MOD_h:    expected_type = type_int;       break; /* TODO promoted short */
378                                         case FMT_MOD_l:    expected_type = type_long;      break;
379                                         case FMT_MOD_ll:   expected_type = type_long_long; break;
380                                         case FMT_MOD_j:    expected_type = type_intmax_t;  break;
381                                         case FMT_MOD_z:    expected_type = type_ssize_t;   break;
382                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t; break;
383                                         case FMT_MOD_I:    expected_type = type_ptrdiff_t; break;
384                                         case FMT_MOD_I32:  expected_type = type_int32;     break;
385                                         case FMT_MOD_I64:  expected_type = type_int64;     break;
386
387                                         default:
388                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
389                                                 goto next_arg;
390                                 }
391                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_SPACE | FMT_FLAG_PLUS | FMT_FLAG_ZERO;
392                                 break;
393
394                         case 'o':
395                         case 'X':
396                         case 'x':
397                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_HASH | FMT_FLAG_ZERO;
398                                 goto eval_fmt_mod_unsigned;
399
400                         case 'u':
401                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_ZERO;
402 eval_fmt_mod_unsigned:
403                                 switch (fmt_mod) {
404                                         case FMT_MOD_NONE: expected_type = type_unsigned_int;       break;
405                                         case FMT_MOD_hh:   expected_type = type_int;                break; /* TODO promoted unsigned char */
406                                         case FMT_MOD_h:    expected_type = type_int;                break; /* TODO promoted unsigned short */
407                                         case FMT_MOD_l:    expected_type = type_unsigned_long;      break;
408                                         case FMT_MOD_ll:   expected_type = type_unsigned_long_long; break;
409                                         case FMT_MOD_j:    expected_type = type_uintmax_t;          break;
410                                         case FMT_MOD_z:    expected_type = type_size_t;             break;
411                                         case FMT_MOD_t:    expected_type = type_uptrdiff_t;         break;
412                                         case FMT_MOD_I:    expected_type = type_size_t;             break;
413                                         case FMT_MOD_I32:  expected_type = type_unsigned_int32;     break;
414                                         case FMT_MOD_I64:  expected_type = type_unsigned_int64;     break;
415
416                                         default:
417                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
418                                                 goto next_arg;
419                                 }
420                                 break;
421
422                         case 'A':
423                         case 'a':
424                         case 'E':
425                         case 'e':
426                         case 'F':
427                         case 'f':
428                         case 'G':
429                         case 'g':
430                                 switch (fmt_mod) {
431                                         case FMT_MOD_l:    /* l modifier is ignored */
432                                         case FMT_MOD_NONE: expected_type = type_double;      break;
433                                         case FMT_MOD_L:    expected_type = type_long_double; break;
434
435                                         default:
436                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
437                                                 goto next_arg;
438                                 }
439                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_SPACE | FMT_FLAG_PLUS | FMT_FLAG_HASH | FMT_FLAG_ZERO;
440                                 break;
441
442                         case 'C':
443                                 if (fmt_mod != FMT_MOD_NONE) {
444                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
445                                         goto next_arg;
446                                 }
447                                 expected_type = type_wchar_t;
448                                 allowed_flags = FMT_FLAG_NONE;
449                                 break;
450
451                         case 'c':
452                                 expected_type = type_int;
453                                 switch (fmt_mod) {
454                                         case FMT_MOD_NONE: expected_type = type_int;     break; /* TODO promoted char */
455                                         case FMT_MOD_l:    expected_type = type_wint_t;  break;
456                                         case FMT_MOD_w:    expected_type = type_wchar_t; break;
457
458                                         default:
459                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
460                                                 goto next_arg;
461                                 }
462                                 allowed_flags = FMT_FLAG_NONE;
463                                 break;
464
465                         case 'S':
466                                 if (fmt_mod != FMT_MOD_NONE) {
467                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
468                                         goto next_arg;
469                                 }
470                                 expected_type = type_wchar_t_ptr;
471                                 expected_qual = TYPE_QUALIFIER_CONST;
472                                 allowed_flags = FMT_FLAG_MINUS;
473                                 break;
474
475                         case 's':
476                                 switch (fmt_mod) {
477                                         case FMT_MOD_NONE: expected_type = type_char_ptr;    break;
478                                         case FMT_MOD_l:    expected_type = type_wchar_t_ptr; break;
479                                         case FMT_MOD_w:    expected_type = type_wchar_t_ptr; break;
480
481                                         default:
482                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
483                                                 goto next_arg;
484                                 }
485                                 expected_qual = TYPE_QUALIFIER_CONST;
486                                 allowed_flags = FMT_FLAG_MINUS;
487                                 break;
488
489                         case 'p':
490                                 if (fmt_mod != FMT_MOD_NONE) {
491                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
492                                         goto next_arg;
493                                 }
494                                 expected_type = type_void_ptr;
495                                 allowed_flags = FMT_FLAG_NONE;
496                                 break;
497
498                         case 'n':
499                                 switch (fmt_mod) {
500                                         case FMT_MOD_NONE: expected_type = type_int_ptr;         break;
501                                         case FMT_MOD_hh:   expected_type = type_signed_char_ptr; break;
502                                         case FMT_MOD_h:    expected_type = type_short_ptr;       break;
503                                         case FMT_MOD_l:    expected_type = type_long_ptr;        break;
504                                         case FMT_MOD_ll:   expected_type = type_long_long_ptr;   break;
505                                         case FMT_MOD_j:    expected_type = type_intmax_t_ptr;    break;
506                                         case FMT_MOD_z:    expected_type = type_ssize_t_ptr;     break;
507                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t_ptr;   break;
508
509                                         default:
510                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
511                                                 goto next_arg;
512                                 }
513                                 allowed_flags = FMT_FLAG_NONE;
514                                 break;
515
516                         default:
517                                 warningf(pos, "encountered unknown conversion specifier '%%%C' at position %u", (wint_t)fmt, num_fmt);
518                                 goto next_arg;
519                 }
520
521                 format_flags_t wrong_flags = fmt_flags & ~allowed_flags;
522                 if (wrong_flags != 0) {
523                         char wrong[8];
524                         int idx = 0;
525                         if (wrong_flags & FMT_FLAG_HASH)  wrong[idx++] = '#';
526                         if (wrong_flags & FMT_FLAG_ZERO)  wrong[idx++] = '0';
527                         if (wrong_flags & FMT_FLAG_MINUS) wrong[idx++] = '-';
528                         if (wrong_flags & FMT_FLAG_SPACE) wrong[idx++] = ' ';
529                         if (wrong_flags & FMT_FLAG_PLUS)  wrong[idx++] = '+';
530                         if (wrong_flags & FMT_FLAG_TICK)  wrong[idx++] = '\'';
531                         wrong[idx] = '\0';
532
533                         warningf(pos, "invalid format flags \"%s\" in conversion specification %%%c at position %u", wrong, fmt, num_fmt);
534                 }
535
536                 if (arg == NULL) {
537                         warningf(pos, "too few arguments for format string");
538                         return;
539                 }
540
541                 {       /* create a scope here to prevent warning about the jump to next_arg */
542                         type_t *const arg_type           = arg->expression->base.type;
543                         type_t *const arg_skip           = skip_typeref(arg_type);
544                         type_t *const expected_type_skip = skip_typeref(expected_type);
545
546                         if (fmt == 'p') {
547                                 /* allow any pointer type for %p, not just void */
548                                 if (is_type_pointer(arg_skip))
549                                         goto next_arg;
550                         }
551
552                         if (is_type_pointer(expected_type_skip)) {
553                                 if (is_type_pointer(arg_skip)) {
554                                         type_t *const exp_to = skip_typeref(expected_type_skip->pointer.points_to);
555                                         type_t *const arg_to = skip_typeref(arg_skip->pointer.points_to);
556                                         if ((arg_to->base.qualifiers & ~expected_qual) == 0 &&
557                                                 get_unqualified_type(arg_to) == exp_to) {
558                                                 goto next_arg;
559                                         }
560                                 }
561                         } else {
562                                 if (get_unqualified_type(arg_skip) == expected_type_skip) {
563                                         goto next_arg;
564                                 }
565                         }
566                         if (is_type_valid(arg_skip)) {
567                                 warningf(pos,
568                                         "argument type '%T' does not match conversion specifier '%%%s%c' at position %u",
569                                         arg_type, get_length_modifier_name(fmt_mod), (char)fmt, num_fmt);
570                         }
571                 }
572 next_arg:
573                 arg = arg->next;
574         }
575         if (!atend(&vchar)) {
576                 warningf(pos, "format string contains NUL");
577         }
578         if (arg != NULL) {
579                 unsigned num_args = num_fmt;
580                 while (arg != NULL) {
581                         ++num_args;
582                         arg = arg->next;
583                 }
584                 warningf(pos, "%u argument%s but only %u format string%s",
585                         num_args, num_args != 1 ? "s" : "",
586                         num_fmt, num_fmt != 1 ? "s" : "");
587         }
588 }
589
590 /**
591  * Check scanf-style format.
592  */
593 static void check_scanf_format(const call_argument_t *arg, const format_spec_t *spec)
594 {
595         /* find format arg */
596         unsigned idx = 0;
597         for (; idx < spec->fmt_idx; ++idx)
598                 arg = arg->next;
599
600         const expression_t *fmt_expr = arg->expression;
601         if (fmt_expr->kind == EXPR_UNARY_CAST_IMPLICIT) {
602                 fmt_expr = fmt_expr->unary.value;
603         }
604
605         vchar_t vchar;
606         if (fmt_expr->kind == EXPR_WIDE_STRING_LITERAL) {
607                 vchar.string   = &fmt_expr->wide_string.value;
608                 vchar.size     = fmt_expr->wide_string.value.size;
609                 vchar.first    = wstring_first;
610                 vchar.next     = wstring_next;
611                 vchar.is_digit = wstring_isdigit;
612         } else if (fmt_expr->kind == EXPR_STRING_LITERAL) {
613                 vchar.string   = &fmt_expr->string.value;
614                 vchar.size     = fmt_expr->string.value.size;
615                 vchar.first    = string_first;
616                 vchar.next     = string_next;
617                 vchar.is_digit = string_isdigit;
618         } else {
619                 return;
620         }
621         /* find the real args */
622         for (; idx < spec->arg_idx; ++idx)
623                 arg = arg->next;
624
625         const source_position_t *pos = &fmt_expr->base.source_position;
626         unsigned fmt     = vchar.first(&vchar);
627         unsigned num_fmt = 0;
628         for (; fmt != '\0'; fmt = vchar.next(&vchar)) {
629                 if (fmt != '%')
630                         continue;
631                 fmt = vchar.next(&vchar);
632
633                 if (fmt == '%')
634                         continue;
635
636                 ++num_fmt;
637
638                 /* length modifier */
639                 format_length_modifier_t fmt_mod;
640                 switch (fmt) {
641                         case 'h':
642                                 fmt = vchar.next(&vchar);
643                                 if (fmt == 'h') {
644                                         fmt = vchar.next(&vchar);
645                                         fmt_mod = FMT_MOD_hh;
646                                 } else {
647                                         fmt_mod = FMT_MOD_h;
648                                 }
649                                 break;
650
651                         case 'l':
652                                 fmt = vchar.next(&vchar);
653                                 if (fmt == 'l') {
654                                         fmt = vchar.next(&vchar);
655                                         fmt_mod = FMT_MOD_ll;
656                                 } else {
657                                         fmt_mod = FMT_MOD_l;
658                                 }
659                                 break;
660
661                         case 'L': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_L;    break;
662                         case 'j': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_j;    break;
663                         case 't': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_t;    break;
664                         case 'z': fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_z;    break;
665                         /* microsoft mode */
666                         case 'w':
667                                 if (c_mode & _MS) {
668                                         fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_w;
669                                 } else {
670                                         fmt_mod = FMT_MOD_NONE;
671                                 }
672                                 break;
673                         case 'I':
674                                 if (c_mode & _MS) {
675                                         fmt = vchar.next(&vchar); fmt_mod = FMT_MOD_I;
676                                         if (fmt == '3') {
677                                                 fmt = vchar.next(&vchar);
678                                                 if (fmt == '2') {
679                                                         fmt = vchar.next(&vchar);
680                                                         fmt_mod = FMT_MOD_I32;
681                                                 } else {
682                                                         /* rewind */
683                                                         --vchar.position;
684                                                 }
685                                         } else if (fmt == '6') {
686                                                 fmt = vchar.next(&vchar);
687                                                 if (fmt == '4') {
688                                                         fmt = vchar.next(&vchar);
689                                                         fmt_mod = FMT_MOD_I64;
690                                                 } else {
691                                                         /* rewind */
692                                                         --vchar.position;
693                                                 }
694                                         }
695                                 } else {
696                                         fmt_mod = FMT_MOD_NONE;
697                                 }
698                                 break;
699                         default:
700                                 fmt_mod = FMT_MOD_NONE;
701                                 break;
702                 }
703
704                 if (fmt == '\0') {
705                         warningf(pos, "dangling %% in format string");
706                         break;
707                 }
708
709                 type_t            *expected_type;
710                 type_qualifiers_t  expected_qual = TYPE_QUALIFIER_NONE;
711                 switch (fmt) {
712                         case 'd':
713                         case 'i':
714                                 switch (fmt_mod) {
715                                         case FMT_MOD_NONE: expected_type = type_int;         break;
716                                         case FMT_MOD_hh:   expected_type = type_signed_char; break;
717                                         case FMT_MOD_h:    expected_type = type_short;       break;
718                                         case FMT_MOD_l:    expected_type = type_long;        break;
719                                         case FMT_MOD_ll:   expected_type = type_long_long;   break;
720                                         case FMT_MOD_j:    expected_type = type_intmax_t;    break;
721                                         case FMT_MOD_z:    expected_type = type_ssize_t;     break;
722                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t;   break;
723                                         case FMT_MOD_I:    expected_type = type_ptrdiff_t;   break;
724                                         case FMT_MOD_I32:  expected_type = type_int32;       break;
725                                         case FMT_MOD_I64:  expected_type = type_int64;       break;
726
727                                         default:
728                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
729                                                 goto next_arg;
730                                 }
731                                 break;
732
733                         case 'o':
734                         case 'X':
735                         case 'x':
736                                 goto eval_fmt_mod_unsigned;
737
738                         case 'u':
739 eval_fmt_mod_unsigned:
740                                 switch (fmt_mod) {
741                                         case FMT_MOD_NONE: expected_type = type_unsigned_int;       break;
742                                         case FMT_MOD_hh:   expected_type = type_unsigned_char;      break;
743                                         case FMT_MOD_h:    expected_type = type_unsigned_short;     break;
744                                         case FMT_MOD_l:    expected_type = type_unsigned_long;      break;
745                                         case FMT_MOD_ll:   expected_type = type_unsigned_long_long; break;
746                                         case FMT_MOD_j:    expected_type = type_uintmax_t;          break;
747                                         case FMT_MOD_z:    expected_type = type_size_t;             break;
748                                         case FMT_MOD_t:    expected_type = type_uptrdiff_t;         break;
749                                         case FMT_MOD_I:    expected_type = type_size_t;             break;
750                                         case FMT_MOD_I32:  expected_type = type_unsigned_int32;     break;
751                                         case FMT_MOD_I64:  expected_type = type_unsigned_int64;     break;
752
753                                         default:
754                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
755                                                 goto next_arg;
756                                 }
757                                 break;
758
759                         case 'A':
760                         case 'a':
761                         case 'E':
762                         case 'e':
763                         case 'F':
764                         case 'f':
765                         case 'G':
766                         case 'g':
767                                 switch (fmt_mod) {
768                                         case FMT_MOD_l:    /* l modifier is ignored */
769                                         case FMT_MOD_NONE: expected_type = type_double;      break;
770                                         case FMT_MOD_L:    expected_type = type_long_double; break;
771
772                                         default:
773                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
774                                                 goto next_arg;
775                                 }
776                                 break;
777
778                         case 'C':
779                                 if (fmt_mod != FMT_MOD_NONE) {
780                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
781                                         goto next_arg;
782                                 }
783                                 expected_type = type_wchar_t;
784                                 break;
785
786                         case 'c':
787                                 expected_type = type_int;
788                                 switch (fmt_mod) {
789                                         case FMT_MOD_NONE: expected_type = type_int;     break; /* TODO promoted char */
790                                         case FMT_MOD_l:    expected_type = type_wint_t;  break;
791                                         case FMT_MOD_w:    expected_type = type_wchar_t; break;
792
793                                         default:
794                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
795                                                 goto next_arg;
796                                 }
797                                 break;
798
799                         case 'S':
800                                 if (fmt_mod != FMT_MOD_NONE) {
801                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
802                                         goto next_arg;
803                                 }
804                                 expected_type = type_wchar_t_ptr;
805                                 expected_qual = TYPE_QUALIFIER_CONST;
806                                 break;
807
808                         case 's':
809                                 switch (fmt_mod) {
810                                         case FMT_MOD_NONE: expected_type = type_char_ptr;    break;
811                                         case FMT_MOD_l:    expected_type = type_wchar_t_ptr; break;
812                                         case FMT_MOD_w:    expected_type = type_wchar_t_ptr; break;
813
814                                         default:
815                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
816                                                 goto next_arg;
817                                 }
818                                 expected_qual = TYPE_QUALIFIER_CONST;
819                                 break;
820
821                         case 'p':
822                                 if (fmt_mod != FMT_MOD_NONE) {
823                                         warn_invalid_length_modifier(pos, fmt_mod, fmt);
824                                         goto next_arg;
825                                 }
826                                 expected_type = type_void_ptr;
827                                 break;
828
829                         case 'n':
830                                 switch (fmt_mod) {
831                                         case FMT_MOD_NONE: expected_type = type_int;         break;
832                                         case FMT_MOD_hh:   expected_type = type_signed_char; break;
833                                         case FMT_MOD_h:    expected_type = type_short;       break;
834                                         case FMT_MOD_l:    expected_type = type_long;        break;
835                                         case FMT_MOD_ll:   expected_type = type_long_long;   break;
836                                         case FMT_MOD_j:    expected_type = type_intmax_t;    break;
837                                         case FMT_MOD_z:    expected_type = type_ssize_t;     break;
838                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t;   break;
839
840                                         default:
841                                                 warn_invalid_length_modifier(pos, fmt_mod, fmt);
842                                                 goto next_arg;
843                                 }
844                                 break;
845
846                         default:
847                                 warningf(pos, "encountered unknown conversion specifier '%%%C' at position %u", (wint_t)fmt, num_fmt);
848                                 goto next_arg;
849                 }
850
851                 if (arg == NULL) {
852                         warningf(pos, "too few arguments for format string");
853                         return;
854                 }
855
856                 {       /* create a scope here to prevent warning about the jump to next_arg */
857                         type_t *const arg_type           = arg->expression->base.type;
858                         type_t *const arg_skip           = skip_typeref(arg_type);
859                         type_t *const expected_type_skip = skip_typeref(expected_type);
860
861                         if (! is_type_pointer(arg_skip))
862                                 goto error_arg_type;
863                         type_t *const ptr_skip = skip_typeref(arg_skip->pointer.points_to);
864
865                         if (fmt == 'p') {
866                                 /* allow any pointer type for %p, not just void */
867                                 if (is_type_pointer(ptr_skip))
868                                         goto next_arg;
869                         }
870
871                         if (is_type_pointer(expected_type_skip)) {
872                                 if (is_type_pointer(ptr_skip)) {
873                                         type_t *const exp_to = skip_typeref(expected_type_skip->pointer.points_to);
874                                         type_t *const arg_to = skip_typeref(ptr_skip->pointer.points_to);
875                                         if ((arg_to->base.qualifiers & ~expected_qual) == 0 &&
876                                                 get_unqualified_type(arg_to) == exp_to) {
877                                                 goto next_arg;
878                                         }
879                                 }
880                         } else {
881                                 if (get_unqualified_type(ptr_skip) == expected_type_skip) {
882                                         goto next_arg;
883                                 }
884                         }
885 error_arg_type:
886                         if (is_type_valid(arg_skip)) {
887                                 warningf(pos,
888                                         "argument type '%T' does not match conversion specifier '%%%s%c' at position %u",
889                                         arg_type, get_length_modifier_name(fmt_mod), (char)fmt, num_fmt);
890                         }
891                 }
892 next_arg:
893                 arg = arg->next;
894         }
895         if (!atend(&vchar)) {
896                 warningf(pos, "format string contains NUL");
897         }
898         if (arg != NULL) {
899                 unsigned num_args = num_fmt;
900                 while (arg != NULL) {
901                         ++num_args;
902                         arg = arg->next;
903                 }
904                 warningf(pos, "%u argument%s but only %u format string%s",
905                         num_args, num_args != 1 ? "s" : "",
906                         num_fmt, num_fmt != 1 ? "s" : "");
907         }
908 }
909
910 static const format_spec_t builtin_table[] = {
911         { "printf",        FORMAT_PRINTF,   0, 1 },
912         { "wprintf",       FORMAT_PRINTF,   0, 1 },
913         { "sprintf",       FORMAT_PRINTF,   1, 2 },
914         { "swprintf",      FORMAT_PRINTF,   1, 2 },
915         { "snprintf",      FORMAT_PRINTF,   2, 3 },
916         { "snwprintf",     FORMAT_PRINTF,   2, 3 },
917         { "fprintf",       FORMAT_PRINTF,   1, 2 },
918         { "fwprintf",      FORMAT_PRINTF,   1, 2 },
919         { "snwprintf",     FORMAT_PRINTF,   2, 3 },
920         { "snwprintf",     FORMAT_PRINTF,   2, 3 },
921
922         { "scanf",         FORMAT_SCANF,    0, 1 },
923         { "wscanf",        FORMAT_SCANF,    0, 1 },
924         { "sscanf",        FORMAT_SCANF,    1, 2 },
925         { "swscanf",       FORMAT_SCANF,    1, 2 },
926         { "fscanf",        FORMAT_SCANF,    1, 2 },
927         { "fwscanf",       FORMAT_SCANF,    1, 2 },
928
929         { "strftime",      FORMAT_STRFTIME, 3, 4 },
930         { "wcstrftime",    FORMAT_STRFTIME, 3, 4 },
931
932         { "strfmon",       FORMAT_STRFMON,  3, 4 },
933
934         /* MS extensions */
935         { "_snprintf",     FORMAT_PRINTF,   2, 3 },
936         { "_snwprintf",    FORMAT_PRINTF,   2, 3 },
937         { "_scrintf",      FORMAT_PRINTF,   0, 1 },
938         { "_scwprintf",    FORMAT_PRINTF,   0, 1 },
939         { "printf_s",      FORMAT_PRINTF,   0, 1 },
940         { "wprintf_s",     FORMAT_PRINTF,   0, 1 },
941         { "sprintf_s",     FORMAT_PRINTF,   3, 4 },
942         { "swprintf_s",    FORMAT_PRINTF,   3, 4 },
943         { "fprintf_s",     FORMAT_PRINTF,   1, 2 },
944         { "fwprintf_s",    FORMAT_PRINTF,   1, 2 },
945         { "_sprintf_l",    FORMAT_PRINTF,   1, 3 },
946         { "_swprintf_l",   FORMAT_PRINTF,   1, 3 },
947         { "_printf_l",     FORMAT_PRINTF,   0, 2 },
948         { "_wprintf_l",    FORMAT_PRINTF,   0, 2 },
949         { "_fprintf_l",    FORMAT_PRINTF,   1, 3 },
950         { "_fwprintf_l",   FORMAT_PRINTF,   1, 3 },
951         { "_printf_s_l",   FORMAT_PRINTF,   0, 2 },
952         { "_wprintf_s_l",  FORMAT_PRINTF,   0, 2 },
953         { "_sprintf_s_l",  FORMAT_PRINTF,   3, 5 },
954         { "_swprintf_s_l", FORMAT_PRINTF,   3, 5 },
955         { "_fprintf_s_l",  FORMAT_PRINTF,   1, 3 },
956         { "_fwprintf_s_l", FORMAT_PRINTF,   1, 3 },
957 };
958
959 void check_format(const call_expression_t *const call)
960 {
961         if (!warning.format)
962                 return;
963
964         const expression_t *const func_expr = call->function;
965         if (func_expr->kind != EXPR_REFERENCE)
966                 return;
967
968         const entity_t        *const entity = func_expr->reference.entity;
969         const call_argument_t *      arg    = call->arguments;
970
971         if (false) {
972                 /* the declaration has a GNU format attribute, check it */
973         } else {
974                 /*
975                  * For some functions we always check the format, even if it was not specified.
976                  * This allows to check format even in MS mode or without header included.
977                  */
978                 const char *const name = entity->base.symbol->string;
979                 for (size_t i = 0; i < sizeof(builtin_table) / sizeof(builtin_table[0]); ++i) {
980                         if (strcmp(name, builtin_table[i].name) == 0) {
981                                 switch (builtin_table[i].fmt_kind) {
982                                 case FORMAT_PRINTF:
983                                         check_printf_format(arg, &builtin_table[i]);
984                                         break;
985                                 case FORMAT_SCANF:
986                                         check_scanf_format(arg, &builtin_table[i]);
987                                         break;
988                                 }
989                                 break;
990                         }
991                 }
992         }
993 }