fixed icc warnings
[cparser] / format_check.c
1 #include <wctype.h>
2
3 #include "ast_t.h"
4 #include "diagnostic.h"
5 #include "format_check.h"
6 #include "types.h"
7
8
9 typedef enum format_flag_t {
10         FMT_FLAG_NONE  = 0,
11         FMT_FLAG_HASH  = 1U << 0,
12         FMT_FLAG_ZERO  = 1U << 1,
13         FMT_FLAG_MINUS = 1U << 2,
14         FMT_FLAG_SPACE = 1U << 3,
15         FMT_FLAG_PLUS  = 1U << 4,
16         FMT_FLAG_TICK  = 1U << 5
17 } format_flag_t;
18
19 typedef unsigned format_flags_t;
20
21 typedef enum format_length_modifier_t {
22         FMT_MOD_NONE,
23         FMT_MOD_L,
24         FMT_MOD_hh,
25         FMT_MOD_h,
26         FMT_MOD_l,
27         FMT_MOD_ll,
28         FMT_MOD_j,
29         FMT_MOD_t,
30         FMT_MOD_z,
31         FMT_MOD_q
32 } format_length_modifier_t;
33
34 static void warn_invalid_length_modifier(const source_position_t pos,
35                                          const format_length_modifier_t mod,
36                                          const char conversion)
37 {
38         static const char* const names[] = {
39                 [FMT_MOD_NONE] = "",
40                 [FMT_MOD_L]    = "L",
41                 [FMT_MOD_hh]   = "hh",
42                 [FMT_MOD_h]    = "h",
43                 [FMT_MOD_l]    = "l",
44                 [FMT_MOD_ll]   = "ll",
45                 [FMT_MOD_j]    = "j",
46                 [FMT_MOD_t]    = "t",
47                 [FMT_MOD_z]    = "z",
48                 [FMT_MOD_q]    = "q"
49         };
50         assert(mod < sizeof(names) / sizeof(*names));
51
52         parse_warning_posf(pos,
53                 "invalid length modifier '%s' for conversion specifier '%%%c'",
54                 names[mod], conversion
55         );
56 }
57
58 static void check_format_arguments(const call_argument_t *const fmt_arg, const call_argument_t* arg)
59 {
60         const expression_t *fmt_expr = fmt_arg->expression;
61         if (fmt_expr->type == EXPR_UNARY_CAST_IMPLICIT) {
62                 fmt_expr = fmt_expr->unary.value;
63         }
64
65         if (fmt_expr->type != EXPR_WIDE_STRING_LITERAL)
66                 return;
67
68         const source_position_t    pos     = fmt_expr->base.source_position;
69         const wide_string_t *const wstring = &fmt_expr->wide_string.value;
70         const wchar_rep_t *fmt = wstring->begin;
71         for (; *fmt != '\0'; ++fmt) {
72                 if (*fmt != '%')
73                         continue;
74                 ++fmt;
75
76                 if (*fmt == '%')
77                         continue;
78
79                 format_flags_t fmt_flags = FMT_FLAG_NONE;
80                 if (*fmt == '0') {
81                         ++fmt;
82                         fmt_flags |= FMT_FLAG_ZERO;
83                 }
84
85                 /* argument selector or minimum field width */
86                 if (iswdigit(*fmt)) {
87                         do {
88                                 ++fmt;
89                         } while (iswdigit(*fmt));
90
91                         /* digit string was ... */
92                         if (*fmt == '$') {
93                                 /* ... argument selector */
94                                 fmt_flags = FMT_FLAG_NONE; /* reset possibly set 0-flag */
95                                 /* TODO implement */
96                                 return;
97                         }
98                         /* ... minimum field width */
99                 } else {
100                         /* flags */
101                         for (;;) {
102                                 format_flags_t flag;
103                                 switch (*fmt) {
104                                         case '#':  flag = FMT_FLAG_HASH;  break;
105                                         case '0':  flag = FMT_FLAG_ZERO;  break;
106                                         case '-':  flag = FMT_FLAG_MINUS; break;
107                                         case '\'': flag = FMT_FLAG_TICK;  break;
108
109                                         case ' ':
110                                                 if (fmt_flags & FMT_FLAG_PLUS) {
111                                                         parse_warning_pos(pos, "' ' is overridden by prior '+' in conversion specification");
112                                                 }
113                                                 flag = FMT_FLAG_SPACE;
114                                                 break;
115
116                                         case '+':
117                                                 if (fmt_flags & FMT_FLAG_SPACE) {
118                                                         parse_warning_pos(pos, "'+' overrides prior ' ' in conversion specification");
119                                                 }
120                                                 flag = FMT_FLAG_PLUS;
121                                                 break;
122
123                                         default: goto break_fmt_flags;
124                                 }
125                                 if (fmt_flags & flag) {
126                                         parse_warning_posf(pos, "repeated flag '%c' in conversion specification", (char)*fmt);
127                                 }
128                                 fmt_flags |= flag;
129                                 ++fmt;
130                         }
131 break_fmt_flags:
132
133                         /* minimum field width */
134                         if (*fmt == '*') {
135                                 if (arg == NULL) {
136                                         parse_warning_pos(pos, "missing argument for '*' field width in conversion specification");
137                                         return;
138                                 }
139                                 const type_t *const arg_type = arg->expression->base.datatype;
140                                 if (arg_type != type_int) {
141                                         parse_warning_pos(pos, "argument for '*' field width in conversion specification is not an 'int', but an '");
142                                         print_type(arg_type);
143                                         fputc('\'', stderr);
144                                 }
145                                 arg = arg->next;
146                         } else {
147                                 while (iswdigit(*fmt)) {
148                                         ++fmt;
149                                 }
150                         }
151                 }
152
153                 /* precision */
154                 if (*fmt == '.') {
155                         ++fmt;
156                         if (*fmt == '*') {
157                                 if (arg == NULL) {
158                                         parse_warning_pos(pos, "missing argument for '*' precision in conversion specification");
159                                         return;
160                                 }
161                                 const type_t *const arg_type = arg->expression->base.datatype;
162                                 if (arg_type != type_int) {
163                                         parse_warning_pos(pos, "argument for '*' precision in conversion specification is not an 'int', but an '");
164                                         print_type(arg_type);
165                                         fputc('\'', stderr);
166                                 }
167                                 arg = arg->next;
168                         } else {
169                                 /* digit string may be omitted */
170                                 while (iswdigit(*fmt)) {
171                                         ++fmt;
172                                 }
173                         }
174                 }
175
176                 /* length modifier */
177                 format_length_modifier_t fmt_mod;
178                 switch (*fmt) {
179                         case 'h':
180                                 ++fmt;
181                                 if (*fmt == 'h') {
182                                         ++fmt;
183                                         fmt_mod = FMT_MOD_hh;
184                                 } else {
185                                         fmt_mod = FMT_MOD_h;
186                                 }
187                                 break;
188
189                         case 'l':
190                                 ++fmt;
191                                 if (*fmt == 'l') {
192                                         ++fmt;
193                                         fmt_mod = FMT_MOD_ll;
194                                 } else {
195                                         fmt_mod = FMT_MOD_l;
196                                 }
197                                 break;
198
199                         case 'L': ++fmt; fmt_mod = FMT_MOD_L;    break;
200                         case 'j': ++fmt; fmt_mod = FMT_MOD_j;    break;
201                         case 't': ++fmt; fmt_mod = FMT_MOD_t;    break;
202                         case 'z': ++fmt; fmt_mod = FMT_MOD_z;    break;
203                         case 'q': ++fmt; fmt_mod = FMT_MOD_q;    break;
204                         default:         fmt_mod = FMT_MOD_NONE; break;
205                 }
206
207                 if (*fmt == '\0') {
208                         parse_warning_pos(pos, "dangling % in format string");
209                         break;
210                 }
211
212                 const type_t   *expected_type;
213                 format_flags_t  allowed_flags;
214                 switch (*fmt) {
215                         case 'd':
216                         case 'i':
217                                 switch (fmt_mod) {
218                                         case FMT_MOD_NONE: expected_type = type_int;       break;
219                                         case FMT_MOD_hh:   expected_type = type_int;       break; /* TODO promoted signed char */
220                                         case FMT_MOD_h:    expected_type = type_int;       break; /* TODO promoted short */
221                                         case FMT_MOD_l:    expected_type = type_long;      break;
222                                         case FMT_MOD_ll:   expected_type = type_long_long; break;
223                                         case FMT_MOD_j:    expected_type = type_intmax_t;  break;
224                                         case FMT_MOD_z:    expected_type = type_ssize_t;   break;
225                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t; break;
226
227                                         default:
228                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
229                                                 break;
230                                 }
231                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_PLUS | FMT_FLAG_ZERO;
232                                 break;
233
234                         case 'o':
235                         case 'X':
236                         case 'x':
237                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_HASH | FMT_FLAG_ZERO;
238                                 goto eval_fmt_mod_unsigned;
239                                 break;
240
241                         case 'u':
242                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_ZERO;
243 eval_fmt_mod_unsigned:
244                                 switch (fmt_mod) {
245                                         case FMT_MOD_NONE: expected_type = type_unsigned_int;       break;
246                                         case FMT_MOD_hh:   expected_type = type_int;                break; /* TODO promoted unsigned char */
247                                         case FMT_MOD_h:    expected_type = type_int;                break; /* TODO promoted unsigned short */
248                                         case FMT_MOD_l:    expected_type = type_unsigned_long;      break;
249                                         case FMT_MOD_ll:   expected_type = type_unsigned_long_long; break;
250                                         case FMT_MOD_j:    expected_type = type_uintmax_t;          break;
251                                         case FMT_MOD_z:    expected_type = type_size_t;             break;
252                                         case FMT_MOD_t:    expected_type = type_uptrdiff_t;         break;
253
254                                         default:
255                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
256                                                 break;
257                                 }
258                                 break;
259
260                         case 'A':
261                         case 'a':
262                         case 'E':
263                         case 'e':
264                         case 'F':
265                         case 'f':
266                         case 'G':
267                         case 'g':
268                                 switch (fmt_mod) {
269                                         case FMT_MOD_l:    /* l modifier is ignored */
270                                         case FMT_MOD_NONE: expected_type = type_double;      break;
271                                         case FMT_MOD_L:    expected_type = type_long_double; break;
272
273                                         default:
274                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
275                                                 break;
276                                 }
277                                 allowed_flags = FMT_FLAG_MINUS | FMT_FLAG_PLUS | FMT_FLAG_HASH | FMT_FLAG_ZERO;
278                                 break;
279
280                         case 'C':
281                                 if (fmt_mod != FMT_MOD_NONE) {
282                                         warn_invalid_length_modifier(pos, fmt_mod, *fmt);
283                                 }
284                                 expected_type = type_wchar_t;
285                                 allowed_flags = FMT_FLAG_NONE;
286                                 break;
287
288                         case 'c':
289                                 expected_type = type_int;
290                                 switch (fmt_mod) {
291                                         case FMT_MOD_NONE: expected_type = type_int;    break; /* TODO promoted char */
292                                         case FMT_MOD_l:    expected_type = type_wint_t; break;
293
294                                         default:
295                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
296                                                 break;
297                                 }
298                                 allowed_flags = FMT_FLAG_NONE;
299                                 break;
300
301                         case 'S':
302                                 if (fmt_mod != FMT_MOD_NONE) {
303                                         warn_invalid_length_modifier(pos, fmt_mod, *fmt);
304                                 }
305                                 expected_type = type_wchar_t_ptr;
306                                 allowed_flags = FMT_FLAG_NONE;
307                                 break;
308
309                         case 's':
310                                 switch (fmt_mod) {
311                                         case FMT_MOD_NONE: expected_type = type_string;      break;
312                                         case FMT_MOD_l:    expected_type = type_wchar_t_ptr; break;
313
314                                         default:
315                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
316                                                 break;
317                                 }
318                                 allowed_flags = FMT_FLAG_NONE;
319                                 break;
320
321                         case 'p':
322                                 if (fmt_mod != FMT_MOD_NONE) {
323                                         warn_invalid_length_modifier(pos, fmt_mod, *fmt);
324                                 }
325                                 expected_type = type_void_ptr;
326                                 allowed_flags = FMT_FLAG_NONE;
327                                 break;
328
329                         case 'n':
330                                 switch (fmt_mod) {
331                                         case FMT_MOD_NONE: expected_type = type_int_ptr;         break;
332                                         case FMT_MOD_hh:   expected_type = type_signed_char_ptr; break;
333                                         case FMT_MOD_h:    expected_type = type_short_ptr;       break;
334                                         case FMT_MOD_l:    expected_type = type_long_ptr;        break;
335                                         case FMT_MOD_ll:   expected_type = type_long_long_ptr;   break;
336                                         case FMT_MOD_j:    expected_type = type_intmax_t_ptr;    break;
337                                         case FMT_MOD_z:    expected_type = type_ssize_t_ptr;     break;
338                                         case FMT_MOD_t:    expected_type = type_ptrdiff_t_ptr;   break;
339
340                                         default:
341                                                 warn_invalid_length_modifier(pos, fmt_mod, *fmt);
342                                                 break;
343                                 }
344                                 allowed_flags = FMT_FLAG_NONE;
345                                 break;
346
347                         default:
348                                 parse_warning_posf(pos, "encountered unknown conversion specifier '%%%C'", (wint_t)*fmt);
349                                 arg = arg->next;
350                                 continue;
351                 }
352
353                 if ((fmt_flags & ~allowed_flags) != 0) {
354                         /* TODO better warning message text */
355                         parse_warning_pos(pos, "invalid format flags in conversion specification");
356                 }
357
358                 if (arg == NULL) {
359                         parse_warning_pos(pos, "too few arguments for format string");
360                         return;
361                 }
362
363                 const type_t *const arg_type = arg->expression->base.datatype;
364                 if (arg_type != expected_type) {
365                         parser_print_warning_prefix_pos(pos);
366                         fputs("argument type '", stderr);
367                         print_type(arg_type);
368                         fprintf(stderr, "' does not match conversion specifier '%%%c'\n", (char)*fmt);
369                 }
370
371                 arg = arg->next;
372         }
373         if (fmt + 1 != wstring->begin + wstring->size) {
374                 parse_warning_pos(pos, "format string contains NUL");
375         }
376         if (arg != NULL) {
377                 parse_warning_pos(pos, "too many arguments for format string");
378         }
379 }
380
381 void check_format(const call_expression_t *const call)
382 {
383         const expression_t *const func_expr = call->function;
384         if (func_expr->type != EXPR_REFERENCE)
385                 return;
386
387         const char            *const name = func_expr->reference.symbol->string;
388         const call_argument_t *      arg  = call->arguments;
389         if (strcmp(name, "wprintf") == 0) { /* TODO gammlig */
390                 check_format_arguments(arg, arg->next);
391         } else if (strcmp(name, "swprintf") == 0) {
392                 arg = arg->next->next; /* skip destination buffer and size */
393                 check_format_arguments(arg, arg->next);
394         }
395 }