X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/f7ff1b0f0792f1f870778404531e68e77832c4a1..136220349968063bef3f249baddba30a24b52ec2:/src/doprnt.c diff --git a/src/doprnt.c b/src/doprnt.c index 7b4bd35d5b..b8eb0f0719 100644 --- a/src/doprnt.c +++ b/src/doprnt.c @@ -1,7 +1,7 @@ /* Output like sprintf to a buffer of specified size. Also takes args differently: pass one pointer to the end of the format string in addition to the format string itself. - Copyright (C) 1985, 2001-2011 Free Software Foundation, Inc. + Copyright (C) 1985, 2001-2012 Free Software Foundation, Inc. This file is part of GNU Emacs. @@ -26,7 +26,7 @@ along with GNU Emacs. If not, see . */ of the (`int') argument, suitable for display in an Emacs buffer. . For %s and %c, when field width is specified (e.g., %25s), it accounts for - the diplay width of each character, according to char-width-table. That + the display width of each character, according to char-width-table. That is, it does not assume that each character takes one column on display. . If the size of the buffer is not enough to produce the formatted string in @@ -70,9 +70,9 @@ along with GNU Emacs. If not, see . */ %character where flags is [+ -0], width is [0-9]+, precision is .[0-9]+, and length - is empty or l or ll. Also, %% in a format stands for a single % in the - output. A % that does not introduce a valid %-sequence causes - undefined behavior. + is empty or l or the value of the pD or pI or pMd (sans "d") macros. + Also, %% in a format stands for a single % in the output. A % that + does not introduce a valid %-sequence causes undefined behavior. The + flag character inserts a + before any positive number, while a space inserts a space before any positive number; these flags only affect %d, %o, @@ -85,11 +85,10 @@ along with GNU Emacs. If not, see . */ modifier: it is supported for %d, %o, and %x conversions of integral arguments, must immediately precede the conversion specifier, and means that the respective argument is to be treated as `long int' or `unsigned long - int'. Similarly, ll (two letter ells) means to use `long long int' or - `unsigned long long int'; this can be used only on hosts that have - these two types. The empty length modifier means to use `int' or - `unsigned int'. EMACS_INT arguments should use the pI macro, which - expands to whatever length modifier is needed for the target host. + int'. Similarly, the value of the pD macro means to use ptrdiff_t, + the value of the pI macro means to use EMACS_INT or EMACS_UINT, the + value of the pMd etc. macros means to use intmax_t or uintmax_t, + and the empty length modifier means `int' or `unsigned int'. The width specifier supplies a lower limit for the length of the printed representation. The padding, if any, normally goes on the left, but it goes @@ -105,13 +104,8 @@ along with GNU Emacs. If not, see . */ #include #include #include - -#ifdef STDC_HEADERS #include -#endif - #include - #include #include "lisp.h" @@ -121,10 +115,6 @@ along with GNU Emacs. If not, see . */ another macro. */ #include "character.h" -#ifndef SIZE_MAX -# define SIZE_MAX ((size_t) -1) -#endif - #ifndef DBL_MAX_10_EXP #define DBL_MAX_10_EXP 308 /* IEEE double */ #endif @@ -141,8 +131,8 @@ along with GNU Emacs. If not, see . */ String arguments are passed as C strings. Integers are passed as C integers. */ -size_t -doprnt (char *buffer, register size_t bufsize, const char *format, +ptrdiff_t +doprnt (char *buffer, ptrdiff_t bufsize, const char *format, const char *format_end, va_list ap) { const char *fmt = format; /* Pointer into format string */ @@ -152,7 +142,7 @@ doprnt (char *buffer, register size_t bufsize, const char *format, char tembuf[DBL_MAX_10_EXP + 100]; /* Size of sprintf_buffer. */ - size_t size_allocated = sizeof (tembuf); + ptrdiff_t size_allocated = sizeof (tembuf); /* Buffer to use for sprintf. Either tembuf or same as BIG_BUFFER. */ char *sprintf_buffer = tembuf; @@ -171,7 +161,7 @@ doprnt (char *buffer, register size_t bufsize, const char *format, if (format_end == 0) format_end = format + strlen (format); - if ((format_end - format + 1) < sizeof (fixed_buffer)) + if (format_end - format < sizeof (fixed_buffer) - 1) fmtcpy = fixed_buffer; else SAFE_ALLOCA (fmtcpy, char *, format_end - format + 1); @@ -183,9 +173,19 @@ doprnt (char *buffer, register size_t bufsize, const char *format, { if (*fmt == '%') /* Check for a '%' character */ { - size_t size_bound = 0; + ptrdiff_t size_bound = 0; EMACS_INT width; /* Columns occupied by STRING on display. */ - int long_flag = 0; + enum { + pDlen = sizeof pD - 1, + pIlen = sizeof pI - 1, + pMlen = sizeof pMd - 2 + }; + enum { + no_modifier, long_modifier, pD_modifier, pI_modifier, pM_modifier + } length_modifier = no_modifier; + static char const modifier_len[] = { 0, 1, pDlen, pIlen, pMlen }; + int maxmlen = max (max (1, pDlen), max (pIlen, pMlen)); + int mlen; fmt++; /* Copy this one %-spec into fmtcpy. */ @@ -200,16 +200,16 @@ doprnt (char *buffer, register size_t bufsize, const char *format, This might be a field width or a precision; e.g. %1.1000f and %1000.1f both might need 1000+ bytes. Parse the width or precision, checking for overflow. */ - size_t n = *fmt - '0'; - while (fmt < format_end + ptrdiff_t n = *fmt - '0'; + while (fmt + 1 < format_end && '0' <= fmt[1] && fmt[1] <= '9') { - /* Avoid size_t overflow. Avoid int overflow too, as + /* Avoid ptrdiff_t, size_t, and int overflow, as many sprintfs mishandle widths greater than INT_MAX. This test is simple but slightly conservative: e.g., (INT_MAX - INT_MAX % 10) is reported as an overflow even when it's not. */ - if (n >= min (INT_MAX, SIZE_MAX) / 10) + if (n >= min (INT_MAX, min (PTRDIFF_MAX, SIZE_MAX)) / 10) error ("Format width or precision too large"); n = n * 10 + fmt[1] - '0'; *string++ = *++fmt; @@ -218,25 +218,37 @@ doprnt (char *buffer, register size_t bufsize, const char *format, if (size_bound < n) size_bound = n; } - else if (*fmt == '-' || *fmt == ' ' || *fmt == '.' || *fmt == '+') - ; - else if (*fmt == 'l') - { - long_flag = 1 + (fmt + 1 < format_end && fmt[1] == 'l'); - fmt += long_flag; - break; - } - else + else if (! (*fmt == '-' || *fmt == ' ' || *fmt == '.' + || *fmt == '+')) break; fmt++; } - if (fmt > format_end) - fmt = format_end; + + /* Check for the length modifiers in textual length order, so + that longer modifiers override shorter ones. */ + for (mlen = 1; mlen <= maxmlen; mlen++) + { + if (format_end - fmt < mlen) + break; + if (mlen == 1 && *fmt == 'l') + length_modifier = long_modifier; + if (mlen == pDlen && memcmp (fmt, pD, pDlen) == 0) + length_modifier = pD_modifier; + if (mlen == pIlen && memcmp (fmt, pI, pIlen) == 0) + length_modifier = pI_modifier; + if (mlen == pMlen && memcmp (fmt, pMd, pMlen) == 0) + length_modifier = pM_modifier; + } + + mlen = modifier_len[length_modifier]; + memcpy (string, fmt + 1, mlen); + string += mlen; + fmt += mlen; *string = 0; /* Make the size bound large enough to handle floating point formats with large numbers. */ - if (size_bound > SIZE_MAX - DBL_MAX_10_EXP - 50) + if (size_bound > min (PTRDIFF_MAX, SIZE_MAX) - DBL_MAX_10_EXP - 50) error ("Format width or precision too large"); size_bound += DBL_MAX_10_EXP + 50; @@ -253,75 +265,89 @@ doprnt (char *buffer, register size_t bufsize, const char *format, switch (*fmt++) { default: - error ("Invalid format operation %%%s%c", - "ll" + 2 - long_flag, fmt[-1]); + error ("Invalid format operation %s", fmtcpy); /* case 'b': */ case 'l': case 'd': - { - int i; - long l; - - if (1 < long_flag) + switch (length_modifier) + { + case no_modifier: { -#ifdef HAVE_LONG_LONG_INT - long long ll = va_arg (ap, long long); - sprintf (sprintf_buffer, fmtcpy, ll); -#else - error ("Invalid format operation %%ll%c", fmt[-1]); -#endif + int v = va_arg (ap, int); + sprintf (sprintf_buffer, fmtcpy, v); } - else if (long_flag) + break; + case long_modifier: { - l = va_arg(ap, long); - sprintf (sprintf_buffer, fmtcpy, l); + long v = va_arg (ap, long); + sprintf (sprintf_buffer, fmtcpy, v); } - else + break; + case pD_modifier: + signed_pD_modifier: { - i = va_arg(ap, int); - sprintf (sprintf_buffer, fmtcpy, i); + ptrdiff_t v = va_arg (ap, ptrdiff_t); + sprintf (sprintf_buffer, fmtcpy, v); } - /* Now copy into final output, truncating as necessary. */ - string = sprintf_buffer; - goto doit; - } + break; + case pI_modifier: + { + EMACS_INT v = va_arg (ap, EMACS_INT); + sprintf (sprintf_buffer, fmtcpy, v); + } + break; + case pM_modifier: + { + intmax_t v = va_arg (ap, intmax_t); + sprintf (sprintf_buffer, fmtcpy, v); + } + break; + } + /* Now copy into final output, truncating as necessary. */ + string = sprintf_buffer; + goto doit; case 'o': case 'x': - { - unsigned u; - unsigned long ul; - - if (1 < long_flag) + switch (length_modifier) + { + case no_modifier: { -#ifdef HAVE_UNSIGNED_LONG_LONG_INT - unsigned long long ull = va_arg (ap, unsigned long long); - sprintf (sprintf_buffer, fmtcpy, ull); -#else - error ("Invalid format operation %%ll%c", fmt[-1]); -#endif + unsigned v = va_arg (ap, unsigned); + sprintf (sprintf_buffer, fmtcpy, v); } - else if (long_flag) + break; + case long_modifier: { - ul = va_arg(ap, unsigned long); - sprintf (sprintf_buffer, fmtcpy, ul); + unsigned long v = va_arg (ap, unsigned long); + sprintf (sprintf_buffer, fmtcpy, v); } - else + break; + case pD_modifier: + goto signed_pD_modifier; + case pI_modifier: { - u = va_arg(ap, unsigned); - sprintf (sprintf_buffer, fmtcpy, u); + EMACS_UINT v = va_arg (ap, EMACS_UINT); + sprintf (sprintf_buffer, fmtcpy, v); } - /* Now copy into final output, truncating as necessary. */ - string = sprintf_buffer; - goto doit; - } + break; + case pM_modifier: + { + uintmax_t v = va_arg (ap, uintmax_t); + sprintf (sprintf_buffer, fmtcpy, v); + } + break; + } + /* Now copy into final output, truncating as necessary. */ + string = sprintf_buffer; + goto doit; case 'f': case 'e': case 'g': { - double d = va_arg(ap, double); + double d = va_arg (ap, double); sprintf (sprintf_buffer, fmtcpy, d); /* Now copy into final output, truncating as necessary. */ string = sprintf_buffer; @@ -335,7 +361,7 @@ doprnt (char *buffer, register size_t bufsize, const char *format, minlen = atoi (&fmtcpy[1]); string = va_arg (ap, char *); tem = strlen (string); - if (tem > MOST_POSITIVE_FIXNUM) + if (STRING_BYTES_BOUND < tem) error ("String for %%s or %%S format is too long"); width = strwidth (string, tem); goto doit1; @@ -344,7 +370,7 @@ doprnt (char *buffer, register size_t bufsize, const char *format, doit: /* Coming here means STRING contains ASCII only. */ tem = strlen (string); - if (tem > MOST_POSITIVE_FIXNUM) + if (STRING_BYTES_BOUND < tem) error ("Format width or precision too large"); width = tem; doit1: @@ -401,7 +427,7 @@ doprnt (char *buffer, register size_t bufsize, const char *format, case 'c': { - int chr = va_arg(ap, int); + int chr = va_arg (ap, int); tem = CHAR_STRING (chr, (unsigned char *) charbuf); string = charbuf; string[tem] = 0; @@ -441,3 +467,65 @@ doprnt (char *buffer, register size_t bufsize, const char *format, SAFE_FREE (); return bufptr - buffer; } + +/* Format to an unbounded buffer BUF. This is like sprintf, except it + is not limited to returning an 'int' so it doesn't have a silly 2 + GiB limit on typical 64-bit hosts. However, it is limited to the + Emacs-style formats that doprnt supports. + + Return the number of bytes put into BUF, excluding the terminating + '\0'. */ +ptrdiff_t +esprintf (char *buf, char const *format, ...) +{ + ptrdiff_t nbytes; + va_list ap; + va_start (ap, format); + nbytes = doprnt (buf, TYPE_MAXIMUM (ptrdiff_t), format, 0, ap); + va_end (ap); + return nbytes; +} + +#if defined HAVE_X_WINDOWS && defined USE_X_TOOLKIT + +/* Format to buffer *BUF of positive size *BUFSIZE, reallocating *BUF + and updating *BUFSIZE if the buffer is too small, and otherwise + behaving line esprintf. When reallocating, free *BUF unless it is + equal to NONHEAPBUF, and if BUFSIZE_MAX is nonnegative then signal + memory exhaustion instead of growing the buffer size past + BUFSIZE_MAX. */ +ptrdiff_t +exprintf (char **buf, ptrdiff_t *bufsize, + char const *nonheapbuf, ptrdiff_t bufsize_max, + char const *format, ...) +{ + ptrdiff_t nbytes; + va_list ap; + va_start (ap, format); + nbytes = evxprintf (buf, bufsize, nonheapbuf, bufsize_max, format, ap); + va_end (ap); + return nbytes; +} + +#endif + +/* Act like exprintf, except take a va_list. */ +ptrdiff_t +evxprintf (char **buf, ptrdiff_t *bufsize, + char const *nonheapbuf, ptrdiff_t bufsize_max, + char const *format, va_list ap) +{ + for (;;) + { + ptrdiff_t nbytes; + va_list ap_copy; + va_copy (ap_copy, ap); + nbytes = doprnt (*buf, *bufsize, format, 0, ap_copy); + va_end (ap_copy); + if (nbytes < *bufsize - 1) + return nbytes; + if (*buf != nonheapbuf) + xfree (*buf); + *buf = xpalloc (NULL, bufsize, 1, bufsize_max, 1); + } +}