1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Portable safe sprintf code.
10 * Author: Kipp E.B. Hickman
15 #include "mozilla/Vector.h"
30 * Note: on some platforms va_list is defined as an array,
31 * and requires array notation.
34 #define VARARGS_ASSIGN(foo, bar) VA_COPY(foo, bar)
35 #elif defined(HAVE_VA_LIST_AS_ARRAY)
36 #define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]
38 #define VARARGS_ASSIGN(foo, bar) (foo) = (bar)
43 bool (*stuff
)(SprintfState
* ss
, const char* sp
, size_t len
);
49 int (*func
)(void* arg
, const char* sp
, uint32_t len
);
54 * Numbered Argument State
58 int type
; // type of the current ap
59 va_list ap
; // point to the corresponding position on ap
62 typedef mozilla::Vector
<NumArgState
, 20, js::SystemAllocPolicy
> NumArgStateVector
;
75 #define TYPE_INTSTR 10
76 #define TYPE_WSTRING 11
77 #define TYPE_UNKNOWN 20
80 #define FLAG_SIGNED 0x2
81 #define FLAG_SPACED 0x4
82 #define FLAG_ZEROS 0x8
86 generic_write(SprintfState
* ss
, const char* src
, size_t srclen
)
88 return (*ss
->stuff
)(ss
, src
, srclen
);
92 generic_write(SprintfState
* ss
, const char16_t
* src
, size_t srclen
)
94 const size_t CHUNK_SIZE
= 64;
95 char chunk
[CHUNK_SIZE
];
100 // FIXME: truncates characters to 8 bits
101 chunk
[j
++] = char(src
[i
++]);
103 if (j
== CHUNK_SIZE
|| i
== srclen
) {
104 if (!(*ss
->stuff
)(ss
, chunk
, j
))
112 // Fill into the buffer using the data in src
113 template <typename Char
>
115 fill2(SprintfState
* ss
, const Char
* src
, int srclen
, int width
, int flags
)
120 if (width
> 0 && (flags
& FLAG_LEFT
) == 0) { // Right adjusting
121 if (flags
& FLAG_ZEROS
)
123 while (--width
>= 0) {
124 if (!(*ss
->stuff
)(ss
, &space
, 1))
129 // Copy out the source data
130 if (!generic_write(ss
, src
, srclen
))
133 if (width
> 0 && (flags
& FLAG_LEFT
) != 0) { // Left adjusting
134 while (--width
>= 0) {
135 if (!(*ss
->stuff
)(ss
, &space
, 1))
143 * Fill a number. The order is: optional-sign zero-filling conversion-digits
146 fill_n(SprintfState
* ss
, const char* src
, int srclen
, int width
, int prec
, int type
, int flags
)
156 if ((type
& 1) == 0) {
157 if (flags
& FLAG_NEG
) {
160 } else if (flags
& FLAG_SIGNED
) {
163 } else if (flags
& FLAG_SPACED
) {
168 cvtwidth
= signwidth
+ srclen
;
172 precwidth
= prec
- srclen
; // Need zero filling
173 cvtwidth
+= precwidth
;
177 if ((flags
& FLAG_ZEROS
) && (prec
< 0)) {
178 if (width
> cvtwidth
) {
179 zerowidth
= width
- cvtwidth
; // Zero filling
180 cvtwidth
+= zerowidth
;
184 if (flags
& FLAG_LEFT
) {
185 if (width
> cvtwidth
) {
186 // Space filling on the right (i.e. left adjusting)
187 rightspaces
= width
- cvtwidth
;
190 if (width
> cvtwidth
) {
191 // Space filling on the left (i.e. right adjusting)
192 leftspaces
= width
- cvtwidth
;
195 while (--leftspaces
>= 0) {
196 if (!(*ss
->stuff
)(ss
, " ", 1))
200 if (!(*ss
->stuff
)(ss
, &sign
, 1))
203 while (--precwidth
>= 0) {
204 if (!(*ss
->stuff
)(ss
, "0", 1))
207 while (--zerowidth
>= 0) {
208 if (!(*ss
->stuff
)(ss
, "0", 1))
211 if (!(*ss
->stuff
)(ss
, src
, uint32_t(srclen
)))
213 while (--rightspaces
>= 0) {
214 if (!(*ss
->stuff
)(ss
, " ", 1))
220 /* Convert a long into its printable form. */
221 static bool cvt_l(SprintfState
* ss
, long num
, int width
, int prec
, int radix
,
222 int type
, int flags
, const char* hexp
)
228 // according to the man page this needs to happen
229 if ((prec
== 0) && (num
== 0))
232 // Converting decimal is a little tricky. In the unsigned case we
233 // need to stop when we hit 10 digits. In the signed case, we can
234 // stop when the number is zero.
235 cvt
= cvtbuf
+ sizeof(cvtbuf
);
238 int digit
= (((unsigned long)num
) % radix
) & 0xF;
239 *--cvt
= hexp
[digit
];
241 num
= (long)(((unsigned long)num
) / radix
);
248 // Now that we have the number converted without its sign, deal with
249 // the sign and zero padding.
250 return fill_n(ss
, cvt
, digits
, width
, prec
, type
, flags
);
253 /* Convert a 64-bit integer into its printable form. */
254 static bool cvt_ll(SprintfState
* ss
, int64_t num
, int width
, int prec
, int radix
,
255 int type
, int flags
, const char* hexp
)
257 // According to the man page, this needs to happen.
258 if (prec
== 0 && num
== 0)
261 // Converting decimal is a little tricky. In the unsigned case we
262 // need to stop when we hit 10 digits. In the signed case, we can
263 // stop when the number is zero.
264 int64_t rad
= int64_t(radix
);
266 char* cvt
= cvtbuf
+ sizeof(cvtbuf
);
269 int64_t quot
= uint64_t(num
) / rad
;
270 int64_t rem
= uint64_t(num
) % rad
;
271 int32_t digit
= int32_t(rem
);
272 *--cvt
= hexp
[digit
& 0xf];
281 // Now that we have the number converted without its sign, deal with
282 // the sign and zero padding.
283 return fill_n(ss
, cvt
, digits
, width
, prec
, type
, flags
);
287 * Convert a double precision floating point number into its printable
290 * XXX stop using sprintf to convert floating point
292 static bool cvt_f(SprintfState
* ss
, double d
, const char* fmt0
, const char* fmt1
)
296 int amount
= fmt1
- fmt0
;
298 MOZ_ASSERT((amount
> 0) && (amount
< (int)sizeof(fin
)));
299 if (amount
>= (int)sizeof(fin
)) {
300 // Totally bogus % command to sprintf. Just ignore it
303 js_memcpy(fin
, fmt0
, (size_t)amount
);
306 // Convert floating point using the native sprintf code
311 MOZ_ASSERT(*p
!= 'L');
316 sprintf(fout
, fin
, d
);
318 // This assert will catch overflow's of fout, when building with
319 // debugging on. At least this way we can track down the evil piece
320 // of calling code and fix it!
321 MOZ_ASSERT(strlen(fout
) < sizeof(fout
));
323 return (*ss
->stuff
)(ss
, fout
, strlen(fout
));
326 static inline const char* generic_null_str(const char*) { return "(null)"; }
327 static inline const char16_t
* generic_null_str(const char16_t
*) { return MOZ_UTF16("(null)"); }
329 static inline size_t generic_strlen(const char* s
) { return strlen(s
); }
330 static inline size_t generic_strlen(const char16_t
* s
) { return js_strlen(s
); }
333 * Convert a string into its printable form. "width" is the output
334 * width. "prec" is the maximum number of characters of "s" to output,
335 * where -1 means until NUL.
337 template <typename Char
>
339 cvt_s(SprintfState
* ss
, const Char
* s
, int width
, int prec
, int flags
)
344 s
= generic_null_str(s
);
346 // Limit string length by precision value
347 int slen
= int(generic_strlen(s
));
348 if (0 < prec
&& prec
< slen
)
352 return fill2(ss
, s
, slen
, width
, flags
);
356 * BuildArgArray stands for Numbered Argument list Sprintf
358 * fmp = "%4$i, %2$d, %3s, %1d";
359 * the number must start from 1, and no gap among them
362 BuildArgArray(const char* fmt
, va_list ap
, NumArgStateVector
& nas
)
364 size_t number
= 0, cn
= 0, i
;
370 // Detemine how many legal % I have got, then allocate space.
374 while ((c
= *p
++) != 0) {
377 if ((c
= *p
++) == '%') // skip %% case
381 if (c
> '9' || c
< '0') {
382 if (c
== '$') { // numbered argument case
386 } else { // non-numbered argument case
401 if (!nas
.growByUninitialized(number
))
404 for (i
= 0; i
< number
; i
++)
405 nas
[i
].type
= TYPE_UNKNOWN
;
412 while ((c
= *p
++) != 0) {
420 while (c
&& c
!= '$') { // should improve error check later
421 cn
= cn
*10 + c
- '0';
425 if (!c
|| cn
< 1 || cn
> number
)
428 // nas[cn] starts from 0, and make sure nas[cn].type is not assigned.
430 if (nas
[cn
].type
!= TYPE_UNKNOWN
)
437 // not supported feature, for the argument is not numbered
441 while ((c
>= '0') && (c
<= '9')) {
449 // not supported feature, for the argument is not numbered
453 while ((c
>= '0') && (c
<= '9')) {
459 nas
[cn
].type
= TYPE_INTN
;
461 nas
[cn
].type
= TYPE_INT16
;
463 } else if (c
== 'L') {
464 // XXX not quite sure here
465 nas
[cn
].type
= TYPE_INT64
;
467 } else if (c
== 'l') {
468 nas
[cn
].type
= TYPE_INT32
;
471 nas
[cn
].type
= TYPE_INT64
;
490 nas
[cn
].type
= TYPE_DOUBLE
;
494 // XXX should use cpp
495 if (sizeof(void*) == sizeof(int32_t)) {
496 nas
[cn
].type
= TYPE_UINT32
;
497 } else if (sizeof(void*) == sizeof(int64_t)) {
498 nas
[cn
].type
= TYPE_UINT64
;
499 } else if (sizeof(void*) == sizeof(int)) {
500 nas
[cn
].type
= TYPE_UINTN
;
502 nas
[cn
].type
= TYPE_UNKNOWN
;
510 // XXX not supported I suppose
512 nas
[cn
].type
= TYPE_UNKNOWN
;
516 nas
[cn
].type
= (nas
[cn
].type
== TYPE_UINT16
) ? TYPE_WSTRING
: TYPE_STRING
;
520 nas
[cn
].type
= TYPE_INTSTR
;
525 nas
[cn
].type
= TYPE_UNKNOWN
;
530 if (nas
[cn
].type
== TYPE_UNKNOWN
)
539 while (cn
< number
) {
540 if (nas
[cn
].type
== TYPE_UNKNOWN
) {
545 VARARGS_ASSIGN(nas
[cn
].ap
, ap
);
547 switch (nas
[cn
].type
) {
551 case TYPE_UINTN
: (void) va_arg(ap
, int); break;
552 case TYPE_INT32
: (void) va_arg(ap
, int32_t); break;
553 case TYPE_UINT32
: (void) va_arg(ap
, uint32_t); break;
554 case TYPE_INT64
: (void) va_arg(ap
, int64_t); break;
555 case TYPE_UINT64
: (void) va_arg(ap
, uint64_t); break;
556 case TYPE_STRING
: (void) va_arg(ap
, char*); break;
557 case TYPE_WSTRING
: (void) va_arg(ap
, char16_t
*); break;
558 case TYPE_INTSTR
: (void) va_arg(ap
, int*); break;
559 case TYPE_DOUBLE
: (void) va_arg(ap
, double); break;
561 default: return false;
571 * The workhorse sprintf code.
574 dosprintf(SprintfState
* ss
, const char* fmt
, va_list ap
)
577 int flags
, width
, prec
, radix
, type
;
590 static const char hex
[] = "0123456789abcdef";
591 static const char HEX
[] = "0123456789ABCDEF";
595 const char* dolPt
= nullptr; // in "%4$.2f", dolPt will point to '.'
597 // Build an argument array, IF the fmt is numbered argument
598 // list style, to contain the Numbered Argument list pointers.
600 NumArgStateVector nas
;
601 if (!BuildArgArray(fmt
, ap
, nas
)) {
602 // the fmt contains error Numbered Argument format, jliu@netscape.com
607 while ((c
= *fmt
++) != 0) {
609 if (!(*ss
->stuff
)(ss
, fmt
- 1, 1))
616 // Gobble up the % format string. Hopefully we have handled all
617 // of the strange cases!
621 // quoting a % with %%
622 if (!(*ss
->stuff
)(ss
, fmt
- 1, 1))
629 // the fmt contains the Numbered Arguments feature
631 while (c
&& c
!= '$') { // should improve error check later
632 i
= (i
* 10) + (c
- '0');
636 if (nas
[i
- 1].type
== TYPE_UNKNOWN
)
644 // Examine optional flags. Note that we do not implement the
645 // '#' flag of sprintf(). The ANSI C spec. of the '#' flag is
646 // somewhat ambiguous and not ideal, which is perhaps why
647 // the various sprintf() implementations are inconsistent
649 while ((c
== '-') || (c
== '+') || (c
== ' ') || (c
== '0')) {
650 if (c
== '-') flags
|= FLAG_LEFT
;
651 if (c
== '+') flags
|= FLAG_SIGNED
;
652 if (c
== ' ') flags
|= FLAG_SPACED
;
653 if (c
== '0') flags
|= FLAG_ZEROS
;
656 if (flags
& FLAG_SIGNED
) flags
&= ~FLAG_SPACED
;
657 if (flags
& FLAG_LEFT
) flags
&= ~FLAG_ZEROS
;
662 width
= va_arg(ap
, int);
665 while ((c
>= '0') && (c
<= '9')) {
666 width
= (width
* 10) + (c
- '0');
677 prec
= va_arg(ap
, int);
680 while ((c
>= '0') && (c
<= '9')) {
681 prec
= (prec
* 10) + (c
- '0');
692 } else if (c
== 'L') {
693 // XXX not quite sure here
696 } else if (c
== 'l') {
708 case 'd': case 'i': // decimal/integer
710 goto fetch_and_convert
;
715 goto fetch_and_convert
;
717 case 'u': // unsigned decimal
720 goto fetch_and_convert
;
722 case 'x': // unsigned hex
725 goto fetch_and_convert
;
727 case 'X': // unsigned HEX
731 goto fetch_and_convert
;
736 u
.l
= va_arg(ap
, int);
743 u
.l
= va_arg(ap
, int) & 0xffff;
746 u
.l
= va_arg(ap
, int);
753 u
.l
= (long)va_arg(ap
, unsigned int);
757 u
.l
= va_arg(ap
, int32_t);
764 u
.l
= (long)va_arg(ap
, uint32_t);
766 if (!cvt_l(ss
, u
.l
, width
, prec
, radix
, type
, flags
, hexp
))
772 u
.ll
= va_arg(ap
, int64_t);
779 u
.ll
= va_arg(ap
, uint64_t);
781 if (!cvt_ll(ss
, u
.ll
, width
, prec
, radix
, type
, flags
, hexp
))
792 u
.d
= va_arg(ap
, double);
795 if (i
< int(sizeof(pattern
))) {
797 js_memcpy(&pattern
[1], dolPt
, size_t(i
));
798 if (!cvt_f(ss
, u
.d
, pattern
, &pattern
[i
+ 1]))
802 if (!cvt_f(ss
, u
.d
, fmt0
, fmt
))
809 if ((flags
& FLAG_LEFT
) == 0) {
810 while (width
-- > 1) {
811 if (!(*ss
->stuff
)(ss
, " ", 1))
818 u
.ch
= va_arg(ap
, int);
819 if (!(*ss
->stuff
)(ss
, &u
.ch
, 1))
823 if (flags
& FLAG_LEFT
) {
824 while (width
-- > 1) {
825 if (!(*ss
->stuff
)(ss
, " ", 1))
832 if (sizeof(void*) == sizeof(int32_t)) {
834 } else if (sizeof(void*) == sizeof(int64_t)) {
836 } else if (sizeof(void*) == sizeof(int)) {
843 goto fetch_and_convert
;
850 // XXX not supported I suppose
856 if(type
== TYPE_INT16
) {
857 u
.ws
= va_arg(ap
, const char16_t
*);
858 if (!cvt_s(ss
, u
.ws
, width
, prec
, flags
))
861 u
.s
= va_arg(ap
, const char*);
862 if (!cvt_s(ss
, u
.s
, width
, prec
, flags
))
868 u
.ip
= va_arg(ap
, int*);
870 *u
.ip
= ss
->cur
- ss
->base
;
875 // Not a % token after all... skip it
879 if (!(*ss
->stuff
)(ss
, "%", 1))
881 if (!(*ss
->stuff
)(ss
, fmt
- 1, 1))
886 // Stuff trailing NUL
887 if (!(*ss
->stuff
)(ss
, "\0", 1))
893 /************************************************************************/
896 * Stuff routine that automatically grows the js_malloc'd output buffer
897 * before it overflows.
900 GrowStuff(SprintfState
* ss
, const char* sp
, size_t len
)
906 off
= ss
->cur
- ss
->base
;
907 if (off
+ len
>= ss
->maxlen
) {
908 /* Grow the buffer */
909 newlen
= ss
->maxlen
+ ((len
> 32) ? len
: 32);
910 newbase
= static_cast<char*>(js_realloc(ss
->base
, newlen
));
912 /* Ran out of memory */
917 ss
->cur
= ss
->base
+ off
;
925 MOZ_ASSERT(size_t(ss
->cur
- ss
->base
) <= ss
->maxlen
);
930 * sprintf into a js_malloc'd buffer
933 JS_smprintf(const char* fmt
, ...)
939 rv
= JS_vsmprintf(fmt
, ap
);
945 * Free memory allocated, for the caller, by JS_smprintf
948 JS_smprintf_free(char* mem
)
954 JS_vsmprintf(const char* fmt
, va_list ap
)
958 ss
.stuff
= GrowStuff
;
962 if (!dosprintf(&ss
, fmt
, ap
)) {
970 * Stuff routine that discards overflow data
973 LimitStuff(SprintfState
* ss
, const char* sp
, size_t len
)
975 size_t limit
= ss
->maxlen
- (ss
->cur
- ss
->base
);
987 * sprintf into a fixed size buffer. Make sure there is a NUL at the end
990 JS_PUBLIC_API(uint32_t)
991 JS_snprintf(char* out
, uint32_t outlen
, const char* fmt
, ...)
996 MOZ_ASSERT(int32_t(outlen
) > 0);
997 if (int32_t(outlen
) <= 0)
1001 rv
= JS_vsnprintf(out
, outlen
, fmt
, ap
);
1006 JS_PUBLIC_API(uint32_t)
1007 JS_vsnprintf(char* out
, uint32_t outlen
, const char* fmt
, va_list ap
)
1012 MOZ_ASSERT(int32_t(outlen
) > 0);
1013 if (int32_t(outlen
) <= 0)
1016 ss
.stuff
= LimitStuff
;
1020 (void) dosprintf(&ss
, fmt
, ap
);
1022 /* If we added chars, and we didn't append a null, do it now. */
1023 if (ss
.cur
!= ss
.base
&& ss
.cur
[-1] != '\0')
1026 n
= ss
.cur
- ss
.base
;
1027 return n
? n
- 1 : n
;
1030 JS_PUBLIC_API(char*)
1031 JS_sprintf_append(char* last
, const char* fmt
, ...)
1037 rv
= JS_vsprintf_append(last
, fmt
, ap
);
1042 JS_PUBLIC_API(char*)
1043 JS_vsprintf_append(char* last
, const char* fmt
, va_list ap
)
1047 ss
.stuff
= GrowStuff
;
1049 size_t lastlen
= strlen(last
);
1051 ss
.cur
= last
+ lastlen
;
1052 ss
.maxlen
= lastlen
;
1058 if (!dosprintf(&ss
, fmt
, ap
)) {