michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Portable safe sprintf code. michael@0: * michael@0: * Author: Kipp E.B. Hickman michael@0: */ michael@0: michael@0: #include "jsprf.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "jspubtd.h" michael@0: #include "jsstr.h" michael@0: #include "jsutil.h" michael@0: michael@0: using namespace js; michael@0: michael@0: /* michael@0: * Note: on some platforms va_list is defined as an array, michael@0: * and requires array notation. michael@0: */ michael@0: #ifdef HAVE_VA_COPY michael@0: #define VARARGS_ASSIGN(foo, bar) VA_COPY(foo, bar) michael@0: #elif defined(HAVE_VA_LIST_AS_ARRAY) michael@0: #define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] michael@0: #else michael@0: #define VARARGS_ASSIGN(foo, bar) (foo) = (bar) michael@0: #endif michael@0: michael@0: struct SprintfState michael@0: { michael@0: int (*stuff)(SprintfState *ss, const char *sp, size_t len); michael@0: michael@0: char *base; michael@0: char *cur; michael@0: size_t maxlen; michael@0: michael@0: int (*func)(void *arg, const char *sp, uint32_t len); michael@0: void *arg; michael@0: }; michael@0: michael@0: /* michael@0: * Numbered Argument State michael@0: */ michael@0: struct NumArgState michael@0: { michael@0: int type; // type of the current ap michael@0: va_list ap; // point to the corresponding position on ap michael@0: }; michael@0: michael@0: const size_t NAS_DEFAULT_NUM = 20; // default number of NumberedArgumentState array michael@0: michael@0: michael@0: #define TYPE_INT16 0 michael@0: #define TYPE_UINT16 1 michael@0: #define TYPE_INTN 2 michael@0: #define TYPE_UINTN 3 michael@0: #define TYPE_INT32 4 michael@0: #define TYPE_UINT32 5 michael@0: #define TYPE_INT64 6 michael@0: #define TYPE_UINT64 7 michael@0: #define TYPE_STRING 8 michael@0: #define TYPE_DOUBLE 9 michael@0: #define TYPE_INTSTR 10 michael@0: #define TYPE_WSTRING 11 michael@0: #define TYPE_UNKNOWN 20 michael@0: michael@0: #define FLAG_LEFT 0x1 michael@0: #define FLAG_SIGNED 0x2 michael@0: #define FLAG_SPACED 0x4 michael@0: #define FLAG_ZEROS 0x8 michael@0: #define FLAG_NEG 0x10 michael@0: michael@0: inline int michael@0: generic_write(SprintfState *ss, const char *src, size_t srclen) michael@0: { michael@0: return (*ss->stuff)(ss, src, srclen); michael@0: } michael@0: michael@0: inline int michael@0: generic_write(SprintfState *ss, const jschar *src, size_t srclen) michael@0: { michael@0: const size_t CHUNK_SIZE = 64; michael@0: char chunk[CHUNK_SIZE]; michael@0: michael@0: int rv = 0; michael@0: size_t j = 0; michael@0: size_t i = 0; michael@0: while (i < srclen) { michael@0: // FIXME: truncates characters to 8 bits michael@0: chunk[j++] = char(src[i++]); michael@0: michael@0: if (j == CHUNK_SIZE || i == srclen) { michael@0: rv = (*ss->stuff)(ss, chunk, j); michael@0: if (rv != 0) michael@0: return rv; michael@0: j = 0; michael@0: } michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: // Fill into the buffer using the data in src michael@0: template michael@0: static int michael@0: fill2(SprintfState *ss, const Char *src, int srclen, int width, int flags) michael@0: { michael@0: char space = ' '; michael@0: int rv; michael@0: michael@0: width -= srclen; michael@0: if (width > 0 && (flags & FLAG_LEFT) == 0) { // Right adjusting michael@0: if (flags & FLAG_ZEROS) michael@0: space = '0'; michael@0: while (--width >= 0) { michael@0: rv = (*ss->stuff)(ss, &space, 1); michael@0: if (rv < 0) michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // Copy out the source data michael@0: rv = generic_write(ss, src, srclen); michael@0: if (rv < 0) michael@0: return rv; michael@0: michael@0: if (width > 0 && (flags & FLAG_LEFT) != 0) { // Left adjusting michael@0: while (--width >= 0) { michael@0: rv = (*ss->stuff)(ss, &space, 1); michael@0: if (rv < 0) michael@0: return rv; michael@0: } michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /* michael@0: * Fill a number. The order is: optional-sign zero-filling conversion-digits michael@0: */ michael@0: static int michael@0: fill_n(SprintfState *ss, const char *src, int srclen, int width, int prec, int type, int flags) michael@0: { michael@0: int zerowidth = 0; michael@0: int precwidth = 0; michael@0: int signwidth = 0; michael@0: int leftspaces = 0; michael@0: int rightspaces = 0; michael@0: int cvtwidth; michael@0: int rv; michael@0: char sign; michael@0: michael@0: if ((type & 1) == 0) { michael@0: if (flags & FLAG_NEG) { michael@0: sign = '-'; michael@0: signwidth = 1; michael@0: } else if (flags & FLAG_SIGNED) { michael@0: sign = '+'; michael@0: signwidth = 1; michael@0: } else if (flags & FLAG_SPACED) { michael@0: sign = ' '; michael@0: signwidth = 1; michael@0: } michael@0: } michael@0: cvtwidth = signwidth + srclen; michael@0: michael@0: if (prec > 0) { michael@0: if (prec > srclen) { michael@0: precwidth = prec - srclen; // Need zero filling michael@0: cvtwidth += precwidth; michael@0: } michael@0: } michael@0: michael@0: if ((flags & FLAG_ZEROS) && (prec < 0)) { michael@0: if (width > cvtwidth) { michael@0: zerowidth = width - cvtwidth; // Zero filling michael@0: cvtwidth += zerowidth; michael@0: } michael@0: } michael@0: michael@0: if (flags & FLAG_LEFT) { michael@0: if (width > cvtwidth) { michael@0: // Space filling on the right (i.e. left adjusting) michael@0: rightspaces = width - cvtwidth; michael@0: } michael@0: } else { michael@0: if (width > cvtwidth) { michael@0: // Space filling on the left (i.e. right adjusting) michael@0: leftspaces = width - cvtwidth; michael@0: } michael@0: } michael@0: while (--leftspaces >= 0) { michael@0: rv = (*ss->stuff)(ss, " ", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: if (signwidth) { michael@0: rv = (*ss->stuff)(ss, &sign, 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: while (--precwidth >= 0) { michael@0: rv = (*ss->stuff)(ss, "0", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: while (--zerowidth >= 0) { michael@0: rv = (*ss->stuff)(ss, "0", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: rv = (*ss->stuff)(ss, src, uint32_t(srclen)); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: while (--rightspaces >= 0) { michael@0: rv = (*ss->stuff)(ss, " ", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /* Convert a long into its printable form. */ michael@0: static int cvt_l(SprintfState *ss, long num, int width, int prec, int radix, michael@0: int type, int flags, const char *hexp) michael@0: { michael@0: char cvtbuf[100]; michael@0: char *cvt; michael@0: int digits; michael@0: michael@0: // according to the man page this needs to happen michael@0: if ((prec == 0) && (num == 0)) { michael@0: return 0; michael@0: } michael@0: michael@0: // Converting decimal is a little tricky. In the unsigned case we michael@0: // need to stop when we hit 10 digits. In the signed case, we can michael@0: // stop when the number is zero. michael@0: cvt = cvtbuf + sizeof(cvtbuf); michael@0: digits = 0; michael@0: while (num) { michael@0: int digit = (((unsigned long)num) % radix) & 0xF; michael@0: *--cvt = hexp[digit]; michael@0: digits++; michael@0: num = (long)(((unsigned long)num) / radix); michael@0: } michael@0: if (digits == 0) { michael@0: *--cvt = '0'; michael@0: digits++; michael@0: } michael@0: michael@0: // Now that we have the number converted without its sign, deal with michael@0: // the sign and zero padding. michael@0: return fill_n(ss, cvt, digits, width, prec, type, flags); michael@0: } michael@0: michael@0: /* Convert a 64-bit integer into its printable form. */ michael@0: static int cvt_ll(SprintfState *ss, int64_t num, int width, int prec, int radix, michael@0: int type, int flags, const char *hexp) michael@0: { michael@0: // According to the man page, this needs to happen. michael@0: if (prec == 0 && num == 0) michael@0: return 0; michael@0: michael@0: // Converting decimal is a little tricky. In the unsigned case we michael@0: // need to stop when we hit 10 digits. In the signed case, we can michael@0: // stop when the number is zero. michael@0: int64_t rad = int64_t(radix); michael@0: char cvtbuf[100]; michael@0: char *cvt = cvtbuf + sizeof(cvtbuf); michael@0: int digits = 0; michael@0: while (num != 0) { michael@0: int64_t quot = uint64_t(num) / rad; michael@0: int64_t rem = uint64_t(num) % rad; michael@0: int32_t digit = int32_t(rem); michael@0: *--cvt = hexp[digit & 0xf]; michael@0: digits++; michael@0: num = quot; michael@0: } michael@0: if (digits == 0) { michael@0: *--cvt = '0'; michael@0: digits++; michael@0: } michael@0: michael@0: // Now that we have the number converted without its sign, deal with michael@0: // the sign and zero padding. michael@0: return fill_n(ss, cvt, digits, width, prec, type, flags); michael@0: } michael@0: michael@0: /* michael@0: * Convert a double precision floating point number into its printable michael@0: * form. michael@0: * michael@0: * XXX stop using sprintf to convert floating point michael@0: */ michael@0: static int cvt_f(SprintfState *ss, double d, const char *fmt0, const char *fmt1) michael@0: { michael@0: char fin[20]; michael@0: char fout[300]; michael@0: int amount = fmt1 - fmt0; michael@0: michael@0: JS_ASSERT((amount > 0) && (amount < (int)sizeof(fin))); michael@0: if (amount >= (int)sizeof(fin)) { michael@0: // Totally bogus % command to sprintf. Just ignore it michael@0: return 0; michael@0: } michael@0: js_memcpy(fin, fmt0, (size_t)amount); michael@0: fin[amount] = 0; michael@0: michael@0: // Convert floating point using the native sprintf code michael@0: #ifdef DEBUG michael@0: { michael@0: const char *p = fin; michael@0: while (*p) { michael@0: JS_ASSERT(*p != 'L'); michael@0: p++; michael@0: } michael@0: } michael@0: #endif michael@0: sprintf(fout, fin, d); michael@0: michael@0: // This assert will catch overflow's of fout, when building with michael@0: // debugging on. At least this way we can track down the evil piece michael@0: // of calling code and fix it! michael@0: JS_ASSERT(strlen(fout) < sizeof(fout)); michael@0: michael@0: return (*ss->stuff)(ss, fout, strlen(fout)); michael@0: } michael@0: michael@0: static inline const char *generic_null_str(const char *) { return "(null)"; } michael@0: static inline const jschar *generic_null_str(const jschar *) { return MOZ_UTF16("(null)"); } michael@0: michael@0: static inline size_t generic_strlen(const char *s) { return strlen(s); } michael@0: static inline size_t generic_strlen(const jschar *s) { return js_strlen(s); } michael@0: michael@0: /* michael@0: * Convert a string into its printable form. "width" is the output michael@0: * width. "prec" is the maximum number of characters of "s" to output, michael@0: * where -1 means until NUL. michael@0: */ michael@0: template michael@0: static int michael@0: cvt_s(SprintfState *ss, const Char *s, int width, int prec, int flags) michael@0: { michael@0: if (prec == 0) michael@0: return 0; michael@0: if (!s) michael@0: s = generic_null_str(s); michael@0: michael@0: // Limit string length by precision value michael@0: int slen = int(generic_strlen(s)); michael@0: if (0 < prec && prec < slen) michael@0: slen = prec; michael@0: michael@0: // and away we go michael@0: return fill2(ss, s, slen, width, flags); michael@0: } michael@0: michael@0: /* michael@0: * BuildArgArray stands for Numbered Argument list Sprintf michael@0: * for example, michael@0: * fmp = "%4$i, %2$d, %3s, %1d"; michael@0: * the number must start from 1, and no gap among them michael@0: */ michael@0: static NumArgState * michael@0: BuildArgArray(const char *fmt, va_list ap, int *rv, NumArgState *nasArray) michael@0: { michael@0: size_t number = 0, cn = 0, i; michael@0: const char *p; michael@0: char c; michael@0: NumArgState *nas; michael@0: michael@0: michael@0: // First pass: michael@0: // Detemine how many legal % I have got, then allocate space. michael@0: michael@0: p = fmt; michael@0: *rv = 0; michael@0: i = 0; michael@0: while ((c = *p++) != 0) { michael@0: if (c != '%') michael@0: continue; michael@0: if ((c = *p++) == '%') // skip %% case michael@0: continue; michael@0: michael@0: while (c != 0) { michael@0: if (c > '9' || c < '0') { michael@0: if (c == '$') { // numbered argument case michael@0: if (i > 0) { michael@0: *rv = -1; michael@0: return nullptr; michael@0: } michael@0: number++; michael@0: } else { // non-numbered argument case michael@0: if (number > 0) { michael@0: *rv = -1; michael@0: return nullptr; michael@0: } michael@0: i = 1; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: c = *p++; michael@0: } michael@0: } michael@0: michael@0: if (number == 0) michael@0: return nullptr; michael@0: michael@0: if (number > NAS_DEFAULT_NUM) { michael@0: nas = (NumArgState *) js_malloc(number * sizeof(NumArgState)); michael@0: if (!nas) { michael@0: *rv = -1; michael@0: return nullptr; michael@0: } michael@0: } else { michael@0: nas = nasArray; michael@0: } michael@0: michael@0: for (i = 0; i < number; i++) michael@0: nas[i].type = TYPE_UNKNOWN; michael@0: michael@0: michael@0: // Second pass: michael@0: // Set nas[].type. michael@0: michael@0: p = fmt; michael@0: while ((c = *p++) != 0) { michael@0: if (c != '%') michael@0: continue; michael@0: c = *p++; michael@0: if (c == '%') michael@0: continue; michael@0: michael@0: cn = 0; michael@0: while (c && c != '$') { // should improve error check later michael@0: cn = cn*10 + c - '0'; michael@0: c = *p++; michael@0: } michael@0: michael@0: if (!c || cn < 1 || cn > number) { michael@0: *rv = -1; michael@0: break; michael@0: } michael@0: michael@0: // nas[cn] starts from 0, and make sure nas[cn].type is not assigned. michael@0: cn--; michael@0: if (nas[cn].type != TYPE_UNKNOWN) michael@0: continue; michael@0: michael@0: c = *p++; michael@0: michael@0: // width michael@0: if (c == '*') { michael@0: // not supported feature, for the argument is not numbered michael@0: *rv = -1; michael@0: break; michael@0: } michael@0: michael@0: while ((c >= '0') && (c <= '9')) { michael@0: c = *p++; michael@0: } michael@0: michael@0: // precision michael@0: if (c == '.') { michael@0: c = *p++; michael@0: if (c == '*') { michael@0: // not supported feature, for the argument is not numbered michael@0: *rv = -1; michael@0: break; michael@0: } michael@0: michael@0: while ((c >= '0') && (c <= '9')) { michael@0: c = *p++; michael@0: } michael@0: } michael@0: michael@0: // size michael@0: nas[cn].type = TYPE_INTN; michael@0: if (c == 'h') { michael@0: nas[cn].type = TYPE_INT16; michael@0: c = *p++; michael@0: } else if (c == 'L') { michael@0: // XXX not quite sure here michael@0: nas[cn].type = TYPE_INT64; michael@0: c = *p++; michael@0: } else if (c == 'l') { michael@0: nas[cn].type = TYPE_INT32; michael@0: c = *p++; michael@0: if (c == 'l') { michael@0: nas[cn].type = TYPE_INT64; michael@0: c = *p++; michael@0: } michael@0: } michael@0: michael@0: // format michael@0: switch (c) { michael@0: case 'd': michael@0: case 'c': michael@0: case 'i': michael@0: case 'o': michael@0: case 'u': michael@0: case 'x': michael@0: case 'X': michael@0: break; michael@0: michael@0: case 'e': michael@0: case 'f': michael@0: case 'g': michael@0: nas[cn].type = TYPE_DOUBLE; michael@0: break; michael@0: michael@0: case 'p': michael@0: // XXX should use cpp michael@0: if (sizeof(void *) == sizeof(int32_t)) { michael@0: nas[cn].type = TYPE_UINT32; michael@0: } else if (sizeof(void *) == sizeof(int64_t)) { michael@0: nas[cn].type = TYPE_UINT64; michael@0: } else if (sizeof(void *) == sizeof(int)) { michael@0: nas[cn].type = TYPE_UINTN; michael@0: } else { michael@0: nas[cn].type = TYPE_UNKNOWN; michael@0: } michael@0: break; michael@0: michael@0: case 'C': michael@0: case 'S': michael@0: case 'E': michael@0: case 'G': michael@0: // XXX not supported I suppose michael@0: JS_ASSERT(0); michael@0: nas[cn].type = TYPE_UNKNOWN; michael@0: break; michael@0: michael@0: case 's': michael@0: nas[cn].type = (nas[cn].type == TYPE_UINT16) ? TYPE_WSTRING : TYPE_STRING; michael@0: break; michael@0: michael@0: case 'n': michael@0: nas[cn].type = TYPE_INTSTR; michael@0: break; michael@0: michael@0: default: michael@0: JS_ASSERT(0); michael@0: nas[cn].type = TYPE_UNKNOWN; michael@0: break; michael@0: } michael@0: michael@0: // get a legal para. michael@0: if (nas[cn].type == TYPE_UNKNOWN) { michael@0: *rv = -1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: michael@0: // Third pass: michael@0: // Fill nas[].ap. michael@0: michael@0: if (*rv < 0) { michael@0: if (nas != nasArray) michael@0: js_free(nas); michael@0: return nullptr; michael@0: } michael@0: michael@0: cn = 0; michael@0: while (cn < number) { michael@0: if (nas[cn].type == TYPE_UNKNOWN) { michael@0: cn++; michael@0: continue; michael@0: } michael@0: michael@0: VARARGS_ASSIGN(nas[cn].ap, ap); michael@0: michael@0: switch (nas[cn].type) { michael@0: case TYPE_INT16: michael@0: case TYPE_UINT16: michael@0: case TYPE_INTN: michael@0: case TYPE_UINTN: (void) va_arg(ap, int); break; michael@0: case TYPE_INT32: (void) va_arg(ap, int32_t); break; michael@0: case TYPE_UINT32: (void) va_arg(ap, uint32_t); break; michael@0: case TYPE_INT64: (void) va_arg(ap, int64_t); break; michael@0: case TYPE_UINT64: (void) va_arg(ap, uint64_t); break; michael@0: case TYPE_STRING: (void) va_arg(ap, char*); break; michael@0: case TYPE_WSTRING: (void) va_arg(ap, jschar*); break; michael@0: case TYPE_INTSTR: (void) va_arg(ap, int*); break; michael@0: case TYPE_DOUBLE: (void) va_arg(ap, double); break; michael@0: michael@0: default: michael@0: if (nas != nasArray) michael@0: js_free(nas); michael@0: *rv = -1; michael@0: return nullptr; michael@0: } michael@0: michael@0: cn++; michael@0: } michael@0: michael@0: michael@0: return nas; michael@0: } michael@0: michael@0: /* michael@0: * The workhorse sprintf code. michael@0: */ michael@0: static int michael@0: dosprintf(SprintfState *ss, const char *fmt, va_list ap) michael@0: { michael@0: char c; michael@0: int flags, width, prec, radix, type; michael@0: union { michael@0: char ch; michael@0: jschar wch; michael@0: int i; michael@0: long l; michael@0: int64_t ll; michael@0: double d; michael@0: const char *s; michael@0: const jschar* ws; michael@0: int *ip; michael@0: } u; michael@0: const char *fmt0; michael@0: static const char hex[] = "0123456789abcdef"; michael@0: static const char HEX[] = "0123456789ABCDEF"; michael@0: const char *hexp; michael@0: int rv, i; michael@0: NumArgState *nas = nullptr; michael@0: NumArgState nasArray[NAS_DEFAULT_NUM]; michael@0: char pattern[20]; michael@0: const char *dolPt = nullptr; // in "%4$.2f", dolPt will point to '.' michael@0: michael@0: // Build an argument array, IF the fmt is numbered argument michael@0: // list style, to contain the Numbered Argument list pointers. michael@0: michael@0: nas = BuildArgArray(fmt, ap, &rv, nasArray); michael@0: if (rv < 0) { michael@0: // the fmt contains error Numbered Argument format, jliu@netscape.com michael@0: JS_ASSERT(0); michael@0: return rv; michael@0: } michael@0: michael@0: while ((c = *fmt++) != 0) { michael@0: if (c != '%') { michael@0: rv = (*ss->stuff)(ss, fmt - 1, 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: continue; michael@0: } michael@0: fmt0 = fmt - 1; michael@0: michael@0: // Gobble up the % format string. Hopefully we have handled all michael@0: // of the strange cases! michael@0: flags = 0; michael@0: c = *fmt++; michael@0: if (c == '%') { michael@0: // quoting a % with %% michael@0: rv = (*ss->stuff)(ss, fmt - 1, 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: if (nas != nullptr) { michael@0: // the fmt contains the Numbered Arguments feature michael@0: i = 0; michael@0: while (c && c != '$') { // should improve error check later michael@0: i = (i * 10) + (c - '0'); michael@0: c = *fmt++; michael@0: } michael@0: michael@0: if (nas[i-1].type == TYPE_UNKNOWN) { michael@0: if (nas && nas != nasArray) michael@0: js_free(nas); michael@0: return -1; michael@0: } michael@0: michael@0: ap = nas[i-1].ap; michael@0: dolPt = fmt; michael@0: c = *fmt++; michael@0: } michael@0: michael@0: // Examine optional flags. Note that we do not implement the michael@0: // '#' flag of sprintf(). The ANSI C spec. of the '#' flag is michael@0: // somewhat ambiguous and not ideal, which is perhaps why michael@0: // the various sprintf() implementations are inconsistent michael@0: // on this feature. michael@0: while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { michael@0: if (c == '-') flags |= FLAG_LEFT; michael@0: if (c == '+') flags |= FLAG_SIGNED; michael@0: if (c == ' ') flags |= FLAG_SPACED; michael@0: if (c == '0') flags |= FLAG_ZEROS; michael@0: c = *fmt++; michael@0: } michael@0: if (flags & FLAG_SIGNED) flags &= ~FLAG_SPACED; michael@0: if (flags & FLAG_LEFT) flags &= ~FLAG_ZEROS; michael@0: michael@0: // width michael@0: if (c == '*') { michael@0: c = *fmt++; michael@0: width = va_arg(ap, int); michael@0: } else { michael@0: width = 0; michael@0: while ((c >= '0') && (c <= '9')) { michael@0: width = (width * 10) + (c - '0'); michael@0: c = *fmt++; michael@0: } michael@0: } michael@0: michael@0: // precision michael@0: prec = -1; michael@0: if (c == '.') { michael@0: c = *fmt++; michael@0: if (c == '*') { michael@0: c = *fmt++; michael@0: prec = va_arg(ap, int); michael@0: } else { michael@0: prec = 0; michael@0: while ((c >= '0') && (c <= '9')) { michael@0: prec = (prec * 10) + (c - '0'); michael@0: c = *fmt++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // size michael@0: type = TYPE_INTN; michael@0: if (c == 'h') { michael@0: type = TYPE_INT16; michael@0: c = *fmt++; michael@0: } else if (c == 'L') { michael@0: // XXX not quite sure here michael@0: type = TYPE_INT64; michael@0: c = *fmt++; michael@0: } else if (c == 'l') { michael@0: type = TYPE_INT32; michael@0: c = *fmt++; michael@0: if (c == 'l') { michael@0: type = TYPE_INT64; michael@0: c = *fmt++; michael@0: } michael@0: } michael@0: michael@0: // format michael@0: hexp = hex; michael@0: switch (c) { michael@0: case 'd': case 'i': // decimal/integer michael@0: radix = 10; michael@0: goto fetch_and_convert; michael@0: michael@0: case 'o': // octal michael@0: radix = 8; michael@0: type |= 1; michael@0: goto fetch_and_convert; michael@0: michael@0: case 'u': // unsigned decimal michael@0: radix = 10; michael@0: type |= 1; michael@0: goto fetch_and_convert; michael@0: michael@0: case 'x': // unsigned hex michael@0: radix = 16; michael@0: type |= 1; michael@0: goto fetch_and_convert; michael@0: michael@0: case 'X': // unsigned HEX michael@0: radix = 16; michael@0: hexp = HEX; michael@0: type |= 1; michael@0: goto fetch_and_convert; michael@0: michael@0: fetch_and_convert: michael@0: switch (type) { michael@0: case TYPE_INT16: michael@0: u.l = va_arg(ap, int); michael@0: if (u.l < 0) { michael@0: u.l = -u.l; michael@0: flags |= FLAG_NEG; michael@0: } michael@0: goto do_long; michael@0: case TYPE_UINT16: michael@0: u.l = va_arg(ap, int) & 0xffff; michael@0: goto do_long; michael@0: case TYPE_INTN: michael@0: u.l = va_arg(ap, int); michael@0: if (u.l < 0) { michael@0: u.l = -u.l; michael@0: flags |= FLAG_NEG; michael@0: } michael@0: goto do_long; michael@0: case TYPE_UINTN: michael@0: u.l = (long)va_arg(ap, unsigned int); michael@0: goto do_long; michael@0: michael@0: case TYPE_INT32: michael@0: u.l = va_arg(ap, int32_t); michael@0: if (u.l < 0) { michael@0: u.l = -u.l; michael@0: flags |= FLAG_NEG; michael@0: } michael@0: goto do_long; michael@0: case TYPE_UINT32: michael@0: u.l = (long)va_arg(ap, uint32_t); michael@0: do_long: michael@0: rv = cvt_l(ss, u.l, width, prec, radix, type, flags, hexp); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: break; michael@0: michael@0: case TYPE_INT64: michael@0: u.ll = va_arg(ap, int64_t); michael@0: if (u.ll < 0) { michael@0: u.ll = -u.ll; michael@0: flags |= FLAG_NEG; michael@0: } michael@0: goto do_longlong; michael@0: case TYPE_UINT64: michael@0: u.ll = va_arg(ap, uint64_t); michael@0: do_longlong: michael@0: rv = cvt_ll(ss, u.ll, width, prec, radix, type, flags, hexp); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case 'e': michael@0: case 'E': michael@0: case 'f': michael@0: case 'g': michael@0: u.d = va_arg(ap, double); michael@0: if (nas != nullptr) { michael@0: i = fmt - dolPt; michael@0: if (i < int(sizeof(pattern))) { michael@0: pattern[0] = '%'; michael@0: js_memcpy(&pattern[1], dolPt, size_t(i)); michael@0: rv = cvt_f(ss, u.d, pattern, &pattern[i + 1]); michael@0: } michael@0: } else michael@0: rv = cvt_f(ss, u.d, fmt0, fmt); michael@0: michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: break; michael@0: michael@0: case 'c': michael@0: if ((flags & FLAG_LEFT) == 0) { michael@0: while (width-- > 1) { michael@0: rv = (*ss->stuff)(ss, " ", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: switch (type) { michael@0: case TYPE_INT16: michael@0: case TYPE_INTN: michael@0: u.ch = va_arg(ap, int); michael@0: rv = (*ss->stuff)(ss, &u.ch, 1); michael@0: break; michael@0: } michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: if (flags & FLAG_LEFT) { michael@0: while (width-- > 1) { michael@0: rv = (*ss->stuff)(ss, " ", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case 'p': michael@0: if (sizeof(void *) == sizeof(int32_t)) { michael@0: type = TYPE_UINT32; michael@0: } else if (sizeof(void *) == sizeof(int64_t)) { michael@0: type = TYPE_UINT64; michael@0: } else if (sizeof(void *) == sizeof(int)) { michael@0: type = TYPE_UINTN; michael@0: } else { michael@0: JS_ASSERT(0); michael@0: break; michael@0: } michael@0: radix = 16; michael@0: goto fetch_and_convert; michael@0: michael@0: #if 0 michael@0: case 'C': michael@0: case 'S': michael@0: case 'E': michael@0: case 'G': michael@0: // XXX not supported I suppose michael@0: JS_ASSERT(0); michael@0: break; michael@0: #endif michael@0: michael@0: case 's': michael@0: if(type == TYPE_INT16) { michael@0: u.ws = va_arg(ap, const jschar*); michael@0: rv = cvt_s(ss, u.ws, width, prec, flags); michael@0: } else { michael@0: u.s = va_arg(ap, const char*); michael@0: rv = cvt_s(ss, u.s, width, prec, flags); michael@0: } michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: break; michael@0: michael@0: case 'n': michael@0: u.ip = va_arg(ap, int*); michael@0: if (u.ip) { michael@0: *u.ip = ss->cur - ss->base; michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: // Not a % token after all... skip it michael@0: #if 0 michael@0: JS_ASSERT(0); michael@0: #endif michael@0: rv = (*ss->stuff)(ss, "%", 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: rv = (*ss->stuff)(ss, fmt - 1, 1); michael@0: if (rv < 0) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Stuff trailing NUL michael@0: rv = (*ss->stuff)(ss, "\0", 1); michael@0: michael@0: if (nas && nas != nasArray) michael@0: js_free(nas); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /************************************************************************/ michael@0: michael@0: /* michael@0: * Stuff routine that automatically grows the js_malloc'd output buffer michael@0: * before it overflows. michael@0: */ michael@0: static int michael@0: GrowStuff(SprintfState *ss, const char *sp, size_t len) michael@0: { michael@0: ptrdiff_t off; michael@0: char *newbase; michael@0: size_t newlen; michael@0: michael@0: off = ss->cur - ss->base; michael@0: if (off + len >= ss->maxlen) { michael@0: /* Grow the buffer */ michael@0: newlen = ss->maxlen + ((len > 32) ? len : 32); michael@0: newbase = static_cast(js_realloc(ss->base, newlen)); michael@0: if (!newbase) { michael@0: /* Ran out of memory */ michael@0: return -1; michael@0: } michael@0: ss->base = newbase; michael@0: ss->maxlen = newlen; michael@0: ss->cur = ss->base + off; michael@0: } michael@0: michael@0: /* Copy data */ michael@0: while (len) { michael@0: --len; michael@0: *ss->cur++ = *sp++; michael@0: } michael@0: MOZ_ASSERT(size_t(ss->cur - ss->base) <= ss->maxlen); michael@0: return 0; michael@0: } michael@0: michael@0: /* michael@0: * sprintf into a js_malloc'd buffer michael@0: */ michael@0: JS_PUBLIC_API(char *) michael@0: JS_smprintf(const char *fmt, ...) michael@0: { michael@0: va_list ap; michael@0: char *rv; michael@0: michael@0: va_start(ap, fmt); michael@0: rv = JS_vsmprintf(fmt, ap); michael@0: va_end(ap); michael@0: return rv; michael@0: } michael@0: michael@0: /* michael@0: * Free memory allocated, for the caller, by JS_smprintf michael@0: */ michael@0: JS_PUBLIC_API(void) michael@0: JS_smprintf_free(char *mem) michael@0: { michael@0: js_free(mem); michael@0: } michael@0: michael@0: JS_PUBLIC_API(char *) michael@0: JS_vsmprintf(const char *fmt, va_list ap) michael@0: { michael@0: SprintfState ss; michael@0: int rv; michael@0: michael@0: ss.stuff = GrowStuff; michael@0: ss.base = 0; michael@0: ss.cur = 0; michael@0: ss.maxlen = 0; michael@0: rv = dosprintf(&ss, fmt, ap); michael@0: if (rv < 0) { michael@0: js_free(ss.base); michael@0: return 0; michael@0: } michael@0: return ss.base; michael@0: } michael@0: michael@0: /* michael@0: * Stuff routine that discards overflow data michael@0: */ michael@0: static int michael@0: LimitStuff(SprintfState *ss, const char *sp, size_t len) michael@0: { michael@0: size_t limit = ss->maxlen - (ss->cur - ss->base); michael@0: michael@0: if (len > limit) michael@0: len = limit; michael@0: while (len) { michael@0: --len; michael@0: *ss->cur++ = *sp++; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: /* michael@0: * sprintf into a fixed size buffer. Make sure there is a NUL at the end michael@0: * when finished. michael@0: */ michael@0: JS_PUBLIC_API(uint32_t) michael@0: JS_snprintf(char *out, uint32_t outlen, const char *fmt, ...) michael@0: { michael@0: va_list ap; michael@0: int rv; michael@0: michael@0: JS_ASSERT(int32_t(outlen) > 0); michael@0: if (int32_t(outlen) <= 0) michael@0: return 0; michael@0: michael@0: va_start(ap, fmt); michael@0: rv = JS_vsnprintf(out, outlen, fmt, ap); michael@0: va_end(ap); michael@0: return rv; michael@0: } michael@0: michael@0: JS_PUBLIC_API(uint32_t) michael@0: JS_vsnprintf(char *out, uint32_t outlen, const char *fmt, va_list ap) michael@0: { michael@0: SprintfState ss; michael@0: uint32_t n; michael@0: michael@0: JS_ASSERT(int32_t(outlen) > 0); michael@0: if (int32_t(outlen) <= 0) { michael@0: return 0; michael@0: } michael@0: michael@0: ss.stuff = LimitStuff; michael@0: ss.base = out; michael@0: ss.cur = out; michael@0: ss.maxlen = outlen; michael@0: (void) dosprintf(&ss, fmt, ap); michael@0: michael@0: /* If we added chars, and we didn't append a null, do it now. */ michael@0: if (ss.cur != ss.base && ss.cur[-1] != '\0') michael@0: ss.cur[-1] = '\0'; michael@0: michael@0: n = ss.cur - ss.base; michael@0: return n ? n - 1 : n; michael@0: } michael@0: michael@0: JS_PUBLIC_API(char *) michael@0: JS_sprintf_append(char *last, const char *fmt, ...) michael@0: { michael@0: va_list ap; michael@0: char *rv; michael@0: michael@0: va_start(ap, fmt); michael@0: rv = JS_vsprintf_append(last, fmt, ap); michael@0: va_end(ap); michael@0: return rv; michael@0: } michael@0: michael@0: JS_PUBLIC_API(char *) michael@0: JS_vsprintf_append(char *last, const char *fmt, va_list ap) michael@0: { michael@0: SprintfState ss; michael@0: int rv; michael@0: michael@0: ss.stuff = GrowStuff; michael@0: if (last) { michael@0: size_t lastlen = strlen(last); michael@0: ss.base = last; michael@0: ss.cur = last + lastlen; michael@0: ss.maxlen = lastlen; michael@0: } else { michael@0: ss.base = 0; michael@0: ss.cur = 0; michael@0: ss.maxlen = 0; michael@0: } michael@0: rv = dosprintf(&ss, fmt, ap); michael@0: if (rv < 0) { michael@0: js_free(ss.base); michael@0: return 0; michael@0: } michael@0: return ss.base; michael@0: } michael@0: michael@0: #undef TYPE_INT16 michael@0: #undef TYPE_UINT16 michael@0: #undef TYPE_INTN michael@0: #undef TYPE_UINTN michael@0: #undef TYPE_INT32 michael@0: #undef TYPE_UINT32 michael@0: #undef TYPE_INT64 michael@0: #undef TYPE_UINT64 michael@0: #undef TYPE_STRING michael@0: #undef TYPE_DOUBLE michael@0: #undef TYPE_INTSTR michael@0: #undef TYPE_WSTRING michael@0: #undef TYPE_UNKNOWN michael@0: michael@0: #undef FLAG_LEFT michael@0: #undef FLAG_SIGNED michael@0: #undef FLAG_SPACED michael@0: #undef FLAG_ZEROS michael@0: #undef FLAG_NEG