fix printf regression with alt-form octal, zero flag, and field width
[musl] / src / stdio / vfprintf.c
index 6766012..e439a07 100644 (file)
@@ -1,11 +1,18 @@
 #include "stdio_impl.h"
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <inttypes.h>
+#include <math.h>
+#include <float.h>
 
 /* Some useful macros */
 
 #define MAX(a,b) ((a)>(b) ? (a) : (b))
 #define MIN(a,b) ((a)<(b) ? (a) : (b))
-#define CONCAT2(x,y) x ## y
-#define CONCAT(x,y) CONCAT2(x,y)
 
 /* Convenient bit representation for modifier flags, which all fall
  * within 31 codepoints of the space character. */
@@ -73,6 +80,8 @@ static const unsigned char states[]['z'-'A'+1] = {
        }, { /* 1: l-prefixed */
                S('d') = LONG, S('i') = LONG,
                S('o') = ULONG, S('u') = ULONG, S('x') = ULONG, S('X') = ULONG,
+               S('e') = DBL, S('f') = DBL, S('g') = DBL, S('a') = DBL,
+               S('E') = DBL, S('F') = DBL, S('G') = DBL, S('A') = DBL,
                S('c') = INT, S('s') = PTR, S('n') = PTR,
                S('l') = LLPRE,
        }, { /* 2: ll-prefixed */
@@ -149,7 +158,7 @@ static void pop_arg(union arg *arg, int type, va_list *ap)
 
 static void out(FILE *f, const char *s, size_t l)
 {
-       __fwritex((void *)s, l, f);
+       if (!(f->flags & F_ERR)) __fwritex((void *)s, l, f);
 }
 
 static void pad(FILE *f, char c, int w, int l, int fl)
@@ -187,9 +196,17 @@ static char *fmt_u(uintmax_t x, char *s)
        return s;
 }
 
+/* Do not override this check. The floating point printing code below
+ * depends on the float.h constants being right. If they are wrong, it
+ * may overflow the stack. */
+#if LDBL_MANT_DIG == 53
+typedef char compiler_defines_long_double_incorrectly[9-(int)sizeof(long double)];
+#endif
+
 static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
 {
-       uint32_t big[(LDBL_MAX_EXP+LDBL_MANT_DIG)/9+1];
+       uint32_t big[(LDBL_MANT_DIG+28)/29 + 1          // mantissa expansion
+               + (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion
        uint32_t *a, *d, *r, *z;
        int e2=0, e, i, j, l;
        char buf[9+LDBL_MANT_DIG/4], *s;
@@ -198,7 +215,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
        char ebuf0[3*sizeof(int)], *ebuf=&ebuf0[3*sizeof(int)], *estr;
 
        pl=1;
-       if (y<0 || 1/y<0) {
+       if (signbit(y)) {
                y=-y;
        } else if (fl & MARK_POS) {
                prefix+=3;
@@ -208,7 +225,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
 
        if (!isfinite(y)) {
                char *s = (t&32)?"inf":"INF";
-               if (y!=y) s=(t&32)?"nan":"NAN", pl=0;
+               if (y!=y) s=(t&32)?"nan":"NAN";
                pad(f, ' ', w, 3+pl, fl&~ZERO_PAD);
                out(f, prefix, pl);
                out(f, s, 3);
@@ -289,13 +306,13 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                        *d = x % 1000000000;
                        carry = x / 1000000000;
                }
-               if (!z[-1] && z>a) z--;
                if (carry) *--a = carry;
+               while (z>a && !z[-1]) z--;
                e2-=sh;
        }
        while (e2<0) {
-               uint32_t carry=0, *z2;
-               int sh=MIN(9,-e2);
+               uint32_t carry=0, *b;
+               int sh=MIN(9,-e2), need=1+(p+LDBL_MANT_DIG/3+8)/9;
                for (d=a; d<z; d++) {
                        uint32_t rm = *d & (1<<sh)-1;
                        *d = (*d>>sh) + carry;
@@ -304,8 +321,8 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                if (!*a) a++;
                if (carry) *z++ = carry;
                /* Avoid (slow!) computation past requested precision */
-               z2 = ((t|32)=='f' ? r : a) + 2 + p/9;
-               z = MIN(z, z2);
+               b = (t|32)=='f' ? r : a;
+               if (z-b > need) z = b+need;
                e2+=sh;
        }
 
@@ -317,14 +334,14 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
        if (j < 9*(z-r-1)) {
                uint32_t x;
                /* We avoid C's broken division of negative numbers */
-               d = r + 1 + (j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP;
+               d = r + 1 + ((j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP);
                j += 9*LDBL_MAX_EXP;
                j %= 9;
                for (i=10, j++; j<9; i*=10, j++);
                x = *d % i;
                /* Are there any significant digits past j? */
                if (x || d+1!=z) {
-                       long double round = CONCAT(0x1p,LDBL_MANT_DIG);
+                       long double round = 2/LDBL_EPSILON;
                        long double small;
                        if (*d/i & 1) round += 2;
                        if (x<i/2) small=0x0.8p0;
@@ -337,15 +354,15 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                                *d = *d + i;
                                while (*d > 999999999) {
                                        *d--=0;
+                                       if (d<a) *--a=0;
                                        (*d)++;
                                }
-                               if (d<a) a=d;
                                for (i=10, e=9*(r-a); *a>=i; i*=10, e++);
                        }
                }
                if (z>d+1) z=d+1;
-               for (; !z[-1] && z>a; z--);
        }
+       for (; z>a && !z[-1]; z--);
        
        if ((t|32)=='g') {
                if (!p) p++;
@@ -428,7 +445,7 @@ static int getint(char **s) {
 static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, int *nl_type)
 {
        char *a, *z, *s=(char *)fmt;
-       unsigned l10n=0, litpct, fl;
+       unsigned l10n=0, fl;
        int w, p;
        union arg arg;
        int argpos;
@@ -453,9 +470,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
 
                /* Handle literal text and %% format specifiers */
                for (a=s; *s && *s!='%'; s++);
-               litpct = strspn(s, "%")/2; /* Optimize %%%% runs */
-               z = s+litpct;
-               s += 2*litpct;
+               for (z=s; s[0]=='%' && s[1]=='%'; z++, s+=2);
                l = z-a;
                if (f) out(f, a, l);
                if (l) continue;
@@ -514,7 +529,6 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                /* Check validity of argument type (nl/normal) */
                if (st==NOARG) {
                        if (argpos>=0) return -1;
-                       else if (!f) continue;
                } else {
                        if (argpos>=0) nl_type[argpos]=st, arg=nl_arg[argpos];
                        else if (f) pop_arg(&arg, st, ap);
@@ -556,7 +570,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                        if (0) {
                case 'o':
                        a = fmt_o(arg.i, z);
-                       if ((fl&ALT_FORM) && arg.i) prefix+=5, pl=1;
+                       if ((fl&ALT_FORM) && p<z-a+1) prefix+=5, pl=1;
                        } if (0) {
                case 'd': case 'i':
                        pl=1;
@@ -570,11 +584,11 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                case 'u':
                        a = fmt_u(arg.i, z);
                        }
+                       if (p>=0) fl &= ~ZERO_PAD;
                        if (!arg.i && !p) {
                                a=z;
                                break;
                        }
-                       if (p>=0) fl &= ~ZERO_PAD;
                        p = MAX(p, z-a + !arg.i);
                        break;
                case 'c':
@@ -584,7 +598,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                case 'm':
                        if (1) a = strerror(errno); else
                case 's':
-                       a = arg.p;
+                       a = arg.p ? arg.p : "(null)";
                        z = memchr(a, 0, p);
                        if (!z) z=a+p;
                        else p=z-a;
@@ -597,12 +611,12 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
                        p = -1;
                case 'S':
                        ws = arg.p;
-                       for (i=0; *ws && (l=wctomb(mb, *ws++))>=0 && l<=0U+p-i; i+=l);
+                       for (i=l=0; i<0U+p && *ws && (l=wctomb(mb, *ws++))>=0 && l<=0U+p-i; i+=l);
                        if (l<0) return -1;
                        p = i;
                        pad(f, ' ', w, p, fl);
                        ws = arg.p;
-                       for (i=0; *ws && i+(l=wctomb(mb, *ws++))<=p; i+=l)
+                       for (i=0; i<0U+p && *ws && i+(l=wctomb(mb, *ws++))<=p; i+=l)
                                out(f, mb, l);
                        pad(f, ' ', w, p, fl^LEFT_ADJ);
                        l = w>p ? w : p;
@@ -636,22 +650,30 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg,
        return 1;
 }
 
-int vfprintf(FILE *f, const char *fmt, va_list ap)
+int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap)
 {
        va_list ap2;
        int nl_type[NL_ARGMAX+1] = {0};
        union arg nl_arg[NL_ARGMAX+1];
        unsigned char internal_buf[80], *saved_buf = 0;
+       int olderr;
        int ret;
 
+       /* the copy allows passing va_list* even if va_list is an array */
        va_copy(ap2, ap);
-       if (printf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) return -1;
+       if (printf_core(0, fmt, &ap2, nl_arg, nl_type) < 0) {
+               va_end(ap2);
+               return -1;
+       }
 
        FLOCK(f);
+       olderr = f->flags & F_ERR;
+       if (f->mode < 1) f->flags &= ~F_ERR;
        if (!f->buf_size) {
                saved_buf = f->buf;
-               f->buf = internal_buf;
+               f->wpos = f->wbase = f->buf = internal_buf;
                f->buf_size = sizeof internal_buf;
+               f->wend = internal_buf + sizeof internal_buf;
        }
        ret = printf_core(f, fmt, &ap2, nl_arg, nl_type);
        if (saved_buf) {
@@ -661,6 +683,8 @@ int vfprintf(FILE *f, const char *fmt, va_list ap)
                f->buf_size = 0;
                f->wpos = f->wbase = f->wend = 0;
        }
+       if (f->flags & F_ERR) ret = -1;
+       f->flags |= olderr;
        FUNLOCK(f);
        va_end(ap2);
        return ret;