]>
Commit | Line | Data |
---|---|---|
320054e8 DG |
1 | #include "stdio_impl.h" |
2 | #include <errno.h> | |
3 | #include <ctype.h> | |
4 | #include <limits.h> | |
5 | #include <string.h> | |
6 | #include <stdarg.h> | |
7 | #include <stddef.h> | |
8 | #include <stdlib.h> | |
9 | #include <wchar.h> | |
10 | #include <inttypes.h> | |
e5f14be3 | 11 | #ifdef __wasilibc_unmodified_upstream // Changes to optimize printf/scanf when long double isn't needed |
320054e8 DG |
12 | #else |
13 | #include "printscan.h" | |
14 | #endif | |
15 | ||
16 | /* Convenient bit representation for modifier flags, which all fall | |
17 | * within 31 codepoints of the space character. */ | |
18 | ||
19 | #define ALT_FORM (1U<<'#'-' ') | |
20 | #define ZERO_PAD (1U<<'0'-' ') | |
21 | #define LEFT_ADJ (1U<<'-'-' ') | |
22 | #define PAD_POS (1U<<' '-' ') | |
23 | #define MARK_POS (1U<<'+'-' ') | |
24 | #define GROUPED (1U<<'\''-' ') | |
25 | ||
26 | #define FLAGMASK (ALT_FORM|ZERO_PAD|LEFT_ADJ|PAD_POS|MARK_POS|GROUPED) | |
27 | ||
28 | /* State machine to accept length modifiers + conversion specifiers. | |
29 | * Result is 0 on failure, or an argument type to pop on success. */ | |
30 | ||
31 | enum { | |
32 | BARE, LPRE, LLPRE, HPRE, HHPRE, BIGLPRE, | |
33 | ZTPRE, JPRE, | |
34 | STOP, | |
35 | PTR, INT, UINT, ULLONG, | |
36 | LONG, ULONG, | |
37 | SHORT, USHORT, CHAR, UCHAR, | |
38 | LLONG, SIZET, IMAX, UMAX, PDIFF, UIPTR, | |
39 | DBL, LDBL, | |
40 | NOARG, | |
41 | MAXSTATE | |
42 | }; | |
43 | ||
44 | #define S(x) [(x)-'A'] | |
45 | ||
46 | static const unsigned char states[]['z'-'A'+1] = { | |
47 | { /* 0: bare types */ | |
48 | S('d') = INT, S('i') = INT, | |
49 | S('o') = UINT, S('u') = UINT, S('x') = UINT, S('X') = UINT, | |
50 | S('e') = DBL, S('f') = DBL, S('g') = DBL, S('a') = DBL, | |
51 | S('E') = DBL, S('F') = DBL, S('G') = DBL, S('A') = DBL, | |
52 | S('c') = CHAR, S('C') = INT, | |
53 | S('s') = PTR, S('S') = PTR, S('p') = UIPTR, S('n') = PTR, | |
54 | S('m') = NOARG, | |
55 | S('l') = LPRE, S('h') = HPRE, S('L') = BIGLPRE, | |
56 | S('z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE, | |
57 | }, { /* 1: l-prefixed */ | |
58 | S('d') = LONG, S('i') = LONG, | |
59 | S('o') = ULONG, S('u') = ULONG, S('x') = ULONG, S('X') = ULONG, | |
60 | S('c') = INT, S('s') = PTR, S('n') = PTR, | |
61 | S('l') = LLPRE, | |
62 | }, { /* 2: ll-prefixed */ | |
63 | S('d') = LLONG, S('i') = LLONG, | |
64 | S('o') = ULLONG, S('u') = ULLONG, | |
65 | S('x') = ULLONG, S('X') = ULLONG, | |
66 | S('n') = PTR, | |
67 | }, { /* 3: h-prefixed */ | |
68 | S('d') = SHORT, S('i') = SHORT, | |
69 | S('o') = USHORT, S('u') = USHORT, | |
70 | S('x') = USHORT, S('X') = USHORT, | |
71 | S('n') = PTR, | |
72 | S('h') = HHPRE, | |
73 | }, { /* 4: hh-prefixed */ | |
74 | S('d') = CHAR, S('i') = CHAR, | |
75 | S('o') = UCHAR, S('u') = UCHAR, | |
76 | S('x') = UCHAR, S('X') = UCHAR, | |
77 | S('n') = PTR, | |
78 | }, { /* 5: L-prefixed */ | |
79 | S('e') = LDBL, S('f') = LDBL, S('g') = LDBL, S('a') = LDBL, | |
80 | S('E') = LDBL, S('F') = LDBL, S('G') = LDBL, S('A') = LDBL, | |
81 | S('n') = PTR, | |
82 | }, { /* 6: z- or t-prefixed (assumed to be same size) */ | |
83 | S('d') = PDIFF, S('i') = PDIFF, | |
84 | S('o') = SIZET, S('u') = SIZET, | |
85 | S('x') = SIZET, S('X') = SIZET, | |
86 | S('n') = PTR, | |
87 | }, { /* 7: j-prefixed */ | |
88 | S('d') = IMAX, S('i') = IMAX, | |
89 | S('o') = UMAX, S('u') = UMAX, | |
90 | S('x') = UMAX, S('X') = UMAX, | |
91 | S('n') = PTR, | |
92 | } | |
93 | }; | |
94 | ||
95 | #define OOB(x) ((unsigned)(x)-'A' > 'z'-'A') | |
96 | ||
97 | union arg | |
98 | { | |
99 | uintmax_t i; | |
100 | #if !defined(__wasilibc_printscan_no_floating_point) | |
101 | #if defined(__wasilibc_printscan_no_long_double) | |
102 | long_double f; | |
103 | #else | |
104 | long double f; | |
105 | #endif | |
106 | #endif | |
107 | void *p; | |
108 | }; | |
109 | ||
110 | static void pop_arg(union arg *arg, int type, va_list *ap) | |
111 | { | |
112 | switch (type) { | |
113 | case PTR: arg->p = va_arg(*ap, void *); | |
114 | break; case INT: arg->i = va_arg(*ap, int); | |
115 | break; case UINT: arg->i = va_arg(*ap, unsigned int); | |
116 | break; case LONG: arg->i = va_arg(*ap, long); | |
117 | break; case ULONG: arg->i = va_arg(*ap, unsigned long); | |
118 | break; case ULLONG: arg->i = va_arg(*ap, unsigned long long); | |
119 | break; case SHORT: arg->i = (short)va_arg(*ap, int); | |
120 | break; case USHORT: arg->i = (unsigned short)va_arg(*ap, int); | |
121 | break; case CHAR: arg->i = (signed char)va_arg(*ap, int); | |
122 | break; case UCHAR: arg->i = (unsigned char)va_arg(*ap, int); | |
123 | break; case LLONG: arg->i = va_arg(*ap, long long); | |
124 | break; case SIZET: arg->i = va_arg(*ap, size_t); | |
125 | break; case IMAX: arg->i = va_arg(*ap, intmax_t); | |
126 | break; case UMAX: arg->i = va_arg(*ap, uintmax_t); | |
127 | break; case PDIFF: arg->i = va_arg(*ap, ptrdiff_t); | |
128 | break; case UIPTR: arg->i = (uintptr_t)va_arg(*ap, void *); | |
129 | #if defined(__wasilibc_printscan_no_floating_point) | |
130 | break; case DBL: | |
131 | break; case LDBL: floating_point_not_supported(); | |
132 | #else | |
133 | break; case DBL: arg->f = va_arg(*ap, double); | |
134 | #if defined(__wasilibc_printscan_no_long_double) | |
135 | break; case LDBL: long_double_not_supported(); | |
136 | #else | |
137 | break; case LDBL: arg->f = va_arg(*ap, long double); | |
138 | #endif | |
139 | #endif | |
140 | } | |
141 | } | |
142 | ||
143 | static void out(FILE *f, const wchar_t *s, size_t l) | |
144 | { | |
145 | while (l-- && !(f->flags & F_ERR)) fputwc(*s++, f); | |
146 | } | |
147 | ||
148 | static int getint(wchar_t **s) { | |
149 | int i; | |
150 | for (i=0; iswdigit(**s); (*s)++) { | |
151 | if (i > INT_MAX/10U || **s-'0' > INT_MAX-10*i) i = -1; | |
152 | else i = 10*i + (**s-'0'); | |
153 | } | |
154 | return i; | |
155 | } | |
156 | ||
157 | static const char sizeprefix['y'-'a'] = { | |
158 | ['a'-'a']='L', ['e'-'a']='L', ['f'-'a']='L', ['g'-'a']='L', | |
159 | ['d'-'a']='j', ['i'-'a']='j', ['o'-'a']='j', ['u'-'a']='j', ['x'-'a']='j', | |
160 | ['p'-'a']='j' | |
161 | }; | |
162 | ||
163 | static int wprintf_core(FILE *f, const wchar_t *fmt, va_list *ap, union arg *nl_arg, int *nl_type) | |
164 | { | |
165 | wchar_t *a, *z, *s=(wchar_t *)fmt; | |
166 | unsigned l10n=0, fl; | |
167 | int w, p, xp; | |
168 | union arg arg; | |
169 | int argpos; | |
170 | unsigned st, ps; | |
171 | int cnt=0, l=0; | |
172 | int i; | |
173 | int t; | |
174 | char *bs; | |
175 | char charfmt[16]; | |
176 | wchar_t wc; | |
177 | ||
178 | for (;;) { | |
179 | /* This error is only specified for snprintf, but since it's | |
180 | * unspecified for other forms, do the same. Stop immediately | |
181 | * on overflow; otherwise %n could produce wrong results. */ | |
182 | if (l > INT_MAX - cnt) goto overflow; | |
183 | ||
184 | /* Update output count, end loop when fmt is exhausted */ | |
185 | cnt += l; | |
186 | if (!*s) break; | |
187 | ||
188 | /* Handle literal text and %% format specifiers */ | |
189 | for (a=s; *s && *s!='%'; s++); | |
190 | for (z=s; s[0]=='%' && s[1]=='%'; z++, s+=2); | |
191 | if (z-a > INT_MAX-cnt) goto overflow; | |
192 | l = z-a; | |
193 | if (f) out(f, a, l); | |
194 | if (l) continue; | |
195 | ||
196 | if (iswdigit(s[1]) && s[2]=='$') { | |
197 | l10n=1; | |
198 | argpos = s[1]-'0'; | |
199 | s+=3; | |
200 | } else { | |
201 | argpos = -1; | |
202 | s++; | |
203 | } | |
204 | ||
205 | /* Read modifier flags */ | |
206 | for (fl=0; (unsigned)*s-' '<32 && (FLAGMASK&(1U<<*s-' ')); s++) | |
207 | fl |= 1U<<*s-' '; | |
208 | ||
209 | /* Read field width */ | |
210 | if (*s=='*') { | |
211 | if (iswdigit(s[1]) && s[2]=='$') { | |
212 | l10n=1; | |
213 | nl_type[s[1]-'0'] = INT; | |
214 | w = nl_arg[s[1]-'0'].i; | |
215 | s+=3; | |
216 | } else if (!l10n) { | |
217 | w = f ? va_arg(*ap, int) : 0; | |
218 | s++; | |
219 | } else goto inval; | |
220 | if (w<0) fl|=LEFT_ADJ, w=-w; | |
221 | } else if ((w=getint(&s))<0) goto overflow; | |
222 | ||
223 | /* Read precision */ | |
224 | if (*s=='.' && s[1]=='*') { | |
225 | if (isdigit(s[2]) && s[3]=='$') { | |
226 | nl_type[s[2]-'0'] = INT; | |
227 | p = nl_arg[s[2]-'0'].i; | |
228 | s+=4; | |
229 | } else if (!l10n) { | |
230 | p = f ? va_arg(*ap, int) : 0; | |
231 | s+=2; | |
232 | } else goto inval; | |
233 | xp = (p>=0); | |
234 | } else if (*s=='.') { | |
235 | s++; | |
236 | p = getint(&s); | |
237 | xp = 1; | |
238 | } else { | |
239 | p = -1; | |
240 | xp = 0; | |
241 | } | |
242 | ||
243 | /* Format specifier state machine */ | |
244 | st=0; | |
245 | do { | |
246 | if (OOB(*s)) goto inval; | |
247 | ps=st; | |
248 | st=states[st]S(*s++); | |
249 | } while (st-1<STOP); | |
250 | if (!st) goto inval; | |
251 | ||
252 | /* Check validity of argument type (nl/normal) */ | |
253 | if (st==NOARG) { | |
254 | if (argpos>=0) goto inval; | |
255 | } else { | |
256 | if (argpos>=0) nl_type[argpos]=st, arg=nl_arg[argpos]; | |
257 | else if (f) pop_arg(&arg, st, ap); | |
258 | else return 0; | |
259 | } | |
260 | ||
261 | if (!f) continue; | |
262 | t = s[-1]; | |
263 | if (ps && (t&15)==3) t&=~32; | |
264 | ||
265 | switch (t) { | |
266 | case 'n': | |
267 | switch(ps) { | |
268 | case BARE: *(int *)arg.p = cnt; break; | |
269 | case LPRE: *(long *)arg.p = cnt; break; | |
270 | case LLPRE: *(long long *)arg.p = cnt; break; | |
271 | case HPRE: *(unsigned short *)arg.p = cnt; break; | |
272 | case HHPRE: *(unsigned char *)arg.p = cnt; break; | |
273 | case ZTPRE: *(size_t *)arg.p = cnt; break; | |
274 | case JPRE: *(uintmax_t *)arg.p = cnt; break; | |
275 | } | |
276 | continue; | |
277 | case 'c': | |
278 | if (w<1) w=1; | |
279 | if (w>1 && !(fl&LEFT_ADJ)) fprintf(f, "%*s", w-1, ""); | |
280 | fputwc(btowc(arg.i), f); | |
281 | if (w>1 && (fl&LEFT_ADJ)) fprintf(f, "%*s", w-1, ""); | |
282 | l = w; | |
283 | continue; | |
284 | case 'C': | |
285 | fputwc(arg.i, f); | |
286 | l = 1; | |
287 | continue; | |
288 | case 'S': | |
289 | a = arg.p; | |
290 | z = a + wcsnlen(a, p<0 ? INT_MAX : p); | |
291 | if (p<0 && *z) goto overflow; | |
292 | p = z-a; | |
293 | if (w<p) w=p; | |
294 | if (!(fl&LEFT_ADJ)) fprintf(f, "%*s", w-p, ""); | |
295 | out(f, a, p); | |
296 | if ((fl&LEFT_ADJ)) fprintf(f, "%*s", w-p, ""); | |
297 | l=w; | |
298 | continue; | |
299 | case 'm': | |
300 | arg.p = strerror(errno); | |
301 | case 's': | |
302 | if (!arg.p) arg.p = "(null)"; | |
303 | bs = arg.p; | |
304 | for (i=l=0; l<(p<0?INT_MAX:p) && (i=mbtowc(&wc, bs, MB_LEN_MAX))>0; bs+=i, l++); | |
305 | if (i<0) return -1; | |
306 | if (p<0 && *bs) goto overflow; | |
307 | p=l; | |
308 | if (w<p) w=p; | |
309 | if (!(fl&LEFT_ADJ)) fprintf(f, "%*s", w-p, ""); | |
310 | bs = arg.p; | |
311 | while (l--) { | |
312 | i=mbtowc(&wc, bs, MB_LEN_MAX); | |
313 | bs+=i; | |
314 | fputwc(wc, f); | |
315 | } | |
316 | if ((fl&LEFT_ADJ)) fprintf(f, "%*s", w-p, ""); | |
317 | l=w; | |
318 | continue; | |
319 | } | |
320 | ||
321 | if (xp && p<0) goto overflow; | |
322 | #if defined(__wasilibc_printscan_no_long_double) | |
ec9f1c39 | 323 | // Omit the 'L' modifier for floating-point cases. |
320054e8 DG |
324 | switch (t|32) { |
325 | case 'a': case 'e': case 'f': case 'g': | |
326 | snprintf(charfmt, sizeof charfmt, "%%%s%s%s%s%s*.*%c", | |
327 | "#"+!(fl & ALT_FORM), | |
328 | "+"+!(fl & MARK_POS), | |
329 | "-"+!(fl & LEFT_ADJ), | |
330 | " "+!(fl & PAD_POS), | |
331 | "0"+!(fl & ZERO_PAD), | |
332 | t); | |
333 | ||
334 | l = fprintf(f, charfmt, w, p, arg.f); | |
335 | break; | |
336 | case 'd': case 'i': case 'o': case 'u': case 'x': case 'p': | |
337 | snprintf(charfmt, sizeof charfmt, "%%%s%s%s%s%s*.*%c%c", | |
338 | "#"+!(fl & ALT_FORM), | |
339 | "+"+!(fl & MARK_POS), | |
340 | "-"+!(fl & LEFT_ADJ), | |
341 | " "+!(fl & PAD_POS), | |
342 | "0"+!(fl & ZERO_PAD), | |
343 | sizeprefix[(t|32)-'a'], t); | |
344 | ||
345 | l = fprintf(f, charfmt, w, p, arg.i); | |
346 | break; | |
347 | } | |
348 | #else | |
349 | snprintf(charfmt, sizeof charfmt, "%%%s%s%s%s%s*.*%c%c", | |
350 | "#"+!(fl & ALT_FORM), | |
351 | "+"+!(fl & MARK_POS), | |
352 | "-"+!(fl & LEFT_ADJ), | |
353 | " "+!(fl & PAD_POS), | |
354 | "0"+!(fl & ZERO_PAD), | |
355 | sizeprefix[(t|32)-'a'], t); | |
356 | ||
357 | switch (t|32) { | |
358 | case 'a': case 'e': case 'f': case 'g': | |
359 | l = fprintf(f, charfmt, w, p, arg.f); | |
360 | break; | |
361 | case 'd': case 'i': case 'o': case 'u': case 'x': case 'p': | |
362 | l = fprintf(f, charfmt, w, p, arg.i); | |
363 | break; | |
364 | } | |
365 | #endif | |
366 | } | |
367 | ||
368 | if (f) return cnt; | |
369 | if (!l10n) return 0; | |
370 | ||
371 | for (i=1; i<=NL_ARGMAX && nl_type[i]; i++) | |
372 | pop_arg(nl_arg+i, nl_type[i], ap); | |
373 | for (; i<=NL_ARGMAX && !nl_type[i]; i++); | |
374 | if (i<=NL_ARGMAX) return -1; | |
375 | return 1; | |
376 | ||
377 | inval: | |
378 | errno = EINVAL; | |
379 | return -1; | |
380 | overflow: | |
381 | errno = EOVERFLOW; | |
382 | return -1; | |
383 | } | |
384 | ||
385 | int vfwprintf(FILE *restrict f, const wchar_t *restrict fmt, va_list ap) | |
386 | { | |
387 | va_list ap2; | |
388 | int nl_type[NL_ARGMAX] = {0}; | |
389 | union arg nl_arg[NL_ARGMAX]; | |
390 | int olderr; | |
391 | int ret; | |
392 | ||
393 | /* the copy allows passing va_list* even if va_list is an array */ | |
394 | va_copy(ap2, ap); | |
395 | if (wprintf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) { | |
396 | va_end(ap2); | |
397 | return -1; | |
398 | } | |
399 | ||
400 | FLOCK(f); | |
401 | fwide(f, 1); | |
402 | olderr = f->flags & F_ERR; | |
403 | f->flags &= ~F_ERR; | |
404 | ret = wprintf_core(f, fmt, &ap2, nl_arg, nl_type); | |
405 | if (f->flags & F_ERR) ret = -1; | |
406 | f->flags |= olderr; | |
407 | FUNLOCK(f); | |
408 | va_end(ap2); | |
409 | return ret; | |
410 | } |