Add forgotten test case for r203059.
[official-gcc.git] / libsanitizer / sanitizer_common / sanitizer_common_interceptors_scanf.inc
blob5b761382d3e31453037aa0f2642e04e0eb939bc4
1 //===-- sanitizer_common_interceptors_scanf.inc -----------------*- C++ -*-===//
2 //
3 // This file is distributed under the University of Illinois Open Source
4 // License. See LICENSE.TXT for details.
5 //
6 //===----------------------------------------------------------------------===//
7 //
8 // Scanf implementation for use in *Sanitizer interceptors.
9 // Follows http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html
10 // with a few common GNU extensions.
12 //===----------------------------------------------------------------------===//
13 #include <stdarg.h>
15 struct ScanfDirective {
16   int argIdx; // argument index, or -1 of not specified ("%n$")
17   int fieldWidth;
18   bool suppressed; // suppress assignment ("*")
19   bool allocate;   // allocate space ("m")
20   char lengthModifier[2];
21   char convSpecifier;
22   bool maybeGnuMalloc;
25 static const char *parse_number(const char *p, int *out) {
26   *out = internal_atoll(p);
27   while (*p >= '0' && *p <= '9')
28     ++p;
29   return p;
32 static bool char_is_one_of(char c, const char *s) {
33   return !!internal_strchr(s, c);
36 // Parse scanf format string. If a valid directive in encountered, it is
37 // returned in dir. This function returns the pointer to the first
38 // unprocessed character, or 0 in case of error.
39 // In case of the end-of-string, a pointer to the closing \0 is returned.
40 static const char *scanf_parse_next(const char *p, bool allowGnuMalloc,
41                                     ScanfDirective *dir) {
42   internal_memset(dir, 0, sizeof(*dir));
43   dir->argIdx = -1;
45   while (*p) {
46     if (*p != '%') {
47       ++p;
48       continue;
49     }
50     ++p;
51     // %%
52     if (*p == '%') {
53       ++p;
54       continue;
55     }
56     if (*p == '\0') {
57       return 0;
58     }
59     // %n$
60     if (*p >= '0' && *p <= '9') {
61       int number;
62       const char *q = parse_number(p, &number);
63       if (*q == '$') {
64         dir->argIdx = number;
65         p = q + 1;
66       }
67       // Otherwise, do not change p. This will be re-parsed later as the field
68       // width.
69     }
70     // *
71     if (*p == '*') {
72       dir->suppressed = true;
73       ++p;
74     }
75     // Field width.
76     if (*p >= '0' && *p <= '9') {
77       p = parse_number(p, &dir->fieldWidth);
78       if (dir->fieldWidth <= 0)
79         return 0;
80     }
81     // m
82     if (*p == 'm') {
83       dir->allocate = true;
84       ++p;
85     }
86     // Length modifier.
87     if (char_is_one_of(*p, "jztLq")) {
88       dir->lengthModifier[0] = *p;
89       ++p;
90     } else if (*p == 'h') {
91       dir->lengthModifier[0] = 'h';
92       ++p;
93       if (*p == 'h') {
94         dir->lengthModifier[1] = 'h';
95         ++p;
96       }
97     } else if (*p == 'l') {
98       dir->lengthModifier[0] = 'l';
99       ++p;
100       if (*p == 'l') {
101         dir->lengthModifier[1] = 'l';
102         ++p;
103       }
104     }
105     // Conversion specifier.
106     dir->convSpecifier = *p++;
107     // Consume %[...] expression.
108     if (dir->convSpecifier == '[') {
109       if (*p == '^')
110         ++p;
111       if (*p == ']')
112         ++p;
113       while (*p && *p != ']')
114         ++p;
115       if (*p == 0)
116         return 0; // unexpected end of string
117                   // Consume the closing ']'.
118       ++p;
119     }
120     // This is unfortunately ambiguous between old GNU extension
121     // of %as, %aS and %a[...] and newer POSIX %a followed by
122     // letters s, S or [.
123     if (allowGnuMalloc && dir->convSpecifier == 'a' &&
124         !dir->lengthModifier[0]) {
125       if (*p == 's' || *p == 'S') {
126         dir->maybeGnuMalloc = true;
127         ++p;
128       } else if (*p == '[') {
129         // Watch for %a[h-j%d], if % appears in the
130         // [...] range, then we need to give up, we don't know
131         // if scanf will parse it as POSIX %a [h-j %d ] or
132         // GNU allocation of string with range dh-j plus %.
133         const char *q = p + 1;
134         if (*q == '^')
135           ++q;
136         if (*q == ']')
137           ++q;
138         while (*q && *q != ']' && *q != '%')
139           ++q;
140         if (*q == 0 || *q == '%')
141           return 0;
142         p = q + 1; // Consume the closing ']'.
143         dir->maybeGnuMalloc = true;
144       }
145     }
146     break;
147   }
148   return p;
151 // Returns true if the character is an integer conversion specifier.
152 static bool scanf_is_integer_conv(char c) {
153   return char_is_one_of(c, "diouxXn");
156 // Returns true if the character is an floating point conversion specifier.
157 static bool scanf_is_float_conv(char c) {
158   return char_is_one_of(c, "aAeEfFgG");
161 // Returns string output character size for string-like conversions,
162 // or 0 if the conversion is invalid.
163 static int scanf_get_char_size(ScanfDirective *dir) {
164   if (char_is_one_of(dir->convSpecifier, "CS")) {
165     // wchar_t
166     return 0;
167   }
169   if (char_is_one_of(dir->convSpecifier, "cs[")) {
170     if (dir->lengthModifier[0] == 'l')
171       // wchar_t
172       return 0;
173     else if (dir->lengthModifier[0] == 0)
174       return sizeof(char);
175     else
176       return 0;
177   }
179   return 0;
182 enum ScanfStoreSize {
183   // Store size not known in advance; can be calculated as strlen() of the
184   // destination buffer.
185   SSS_STRLEN = -1,
186   // Invalid conversion specifier.
187   SSS_INVALID = 0
190 // Returns the store size of a scanf directive (if >0), or a value of
191 // ScanfStoreSize.
192 static int scanf_get_store_size(ScanfDirective *dir) {
193   if (dir->allocate) {
194     if (!char_is_one_of(dir->convSpecifier, "cCsS["))
195       return SSS_INVALID;
196     return sizeof(char *);
197   }
199   if (dir->maybeGnuMalloc) {
200     if (dir->convSpecifier != 'a' || dir->lengthModifier[0])
201       return SSS_INVALID;
202     // This is ambiguous, so check the smaller size of char * (if it is
203     // a GNU extension of %as, %aS or %a[...]) and float (if it is
204     // POSIX %a followed by s, S or [ letters).
205     return sizeof(char *) < sizeof(float) ? sizeof(char *) : sizeof(float);
206   }
208   if (scanf_is_integer_conv(dir->convSpecifier)) {
209     switch (dir->lengthModifier[0]) {
210     case 'h':
211       return dir->lengthModifier[1] == 'h' ? sizeof(char) : sizeof(short);
212     case 'l':
213       return dir->lengthModifier[1] == 'l' ? sizeof(long long) : sizeof(long);
214     case 'L':
215       return sizeof(long long);
216     case 'j':
217       return sizeof(INTMAX_T);
218     case 'z':
219       return sizeof(SIZE_T);
220     case 't':
221       return sizeof(PTRDIFF_T);
222     case 0:
223       return sizeof(int);
224     default:
225       return SSS_INVALID;
226     }
227   }
229   if (scanf_is_float_conv(dir->convSpecifier)) {
230     switch (dir->lengthModifier[0]) {
231     case 'L':
232     case 'q':
233       return sizeof(long double);
234     case 'l':
235       return dir->lengthModifier[1] == 'l' ? sizeof(long double)
236                                            : sizeof(double);
237     case 0:
238       return sizeof(float);
239     default:
240       return SSS_INVALID;
241     }
242   }
244   if (char_is_one_of(dir->convSpecifier, "sS[")) {
245     unsigned charSize = scanf_get_char_size(dir);
246     if (charSize == 0)
247       return SSS_INVALID;
248     if (dir->fieldWidth == 0)
249       return SSS_STRLEN;
250     return (dir->fieldWidth + 1) * charSize;
251   }
253   if (char_is_one_of(dir->convSpecifier, "cC")) {
254     unsigned charSize = scanf_get_char_size(dir);
255     if (charSize == 0)
256       return SSS_INVALID;
257     if (dir->fieldWidth == 0)
258       return charSize;
259     return dir->fieldWidth * charSize;
260   }
262   if (dir->convSpecifier == 'p') {
263     if (dir->lengthModifier[1] != 0)
264       return SSS_INVALID;
265     return sizeof(void *);
266   }
268   return SSS_INVALID;
271 // Common part of *scanf interceptors.
272 // Process format string and va_list, and report all store ranges.
273 // Stops when "consuming" n_inputs input items.
274 static void scanf_common(void *ctx, int n_inputs, bool allowGnuMalloc,
275                          const char *format, va_list aq) {
276   CHECK_GT(n_inputs, 0);
277   const char *p = format;
279   while (*p && n_inputs) {
280     ScanfDirective dir;
281     p = scanf_parse_next(p, allowGnuMalloc, &dir);
282     if (!p)
283       break;
284     if (dir.convSpecifier == 0) {
285       // This can only happen at the end of the format string.
286       CHECK_EQ(*p, 0);
287       break;
288     }
289     // Here the directive is valid. Do what it says.
290     if (dir.argIdx != -1) {
291       // Unsupported.
292       break;
293     }
294     if (dir.suppressed)
295       continue;
296     int size = scanf_get_store_size(&dir);
297     if (size == SSS_INVALID)
298       break;
299     void *argp = va_arg(aq, void *);
300     if (dir.convSpecifier != 'n')
301       --n_inputs;
302     if (size == SSS_STRLEN) {
303       size = internal_strlen((const char *)argp) + 1;
304     }
305     COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size);
306   }