fix printf regression with alt-form octal, zero flag, and field width
[musl] / src / stdio / vfprintf.c
index d186d58..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. */
@@ -151,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)
@@ -189,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;
@@ -200,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;
@@ -210,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);
@@ -291,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, *b;
-               int sh=MIN(9,-e2);
+               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;
@@ -307,7 +322,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                if (carry) *z++ = carry;
                /* Avoid (slow!) computation past requested precision */
                b = (t|32)=='f' ? r : a;
-               if (z-b > 2+p/9) z = b+2+p/9;
+               if (z-b > need) z = b+need;
                e2+=sh;
        }
 
@@ -326,7 +341,7 @@ static int fmt_fp(FILE *f, long double y, int w, int p, int fl, int t)
                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;
@@ -339,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++;
@@ -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;
@@ -636,18 +650,25 @@ 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->wpos = f->wbase = f->buf = internal_buf;
@@ -662,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;