1 /*
2 * Minimal vsnprintf(), snprintf(), sprintf(), and sscanf() for Duktape.
3 * The supported conversion formats narrowly match what Duktape needs.
4 */
5
6 #include <stdarg.h> /* va_list etc */
7 #include <stddef.h> /* size_t */
8 #include <stdint.h> /* SIZE_MAX */
9
10 /* Write character with bound checking. Offset 'off' is updated regardless
11 * of whether an actual write is made. This is necessary to satisfy snprintf()
12 * return value semantics.
13 */
14 #define DUK__WRITE_CHAR(c) do { \
15 if (off < size) { \
16 str[off] = (char) c; \
17 } \
18 off++; \
19 } while (0)
20
21 /* Digits up to radix 16. */
22 static const char duk__format_digits[16] = {
23 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
24 };
25
26 /* Format an unsigned long with various options. An unsigned long is large
27 * enough for formatting all supported types.
28 */
duk__format_long(char * str,size_t size,size_t off,int fixed_length,char pad,int radix,int neg_sign,unsigned long v)29 static size_t duk__format_long(char *str,
30 size_t size,
31 size_t off,
32 int fixed_length,
33 char pad,
34 int radix,
35 int neg_sign,
36 unsigned long v) {
37 char buf[24]; /* 2^64 = 18446744073709552000, length 20 */
38 char *required;
39 char *p;
40 int i;
41
42 /* Format in reverse order first. Ensure at least one digit is output
43 * to handle '0' correctly. Note that space padding and zero padding
44 * handle negative sign differently:
45 *
46 * %9d and -321 => ' -321'
47 * %09d and -321 => '-00000321'
48 */
49
50 for (i = 0; i < (int) sizeof(buf); i++) {
51 buf[i] = pad; /* compiles into memset() equivalent, avoid memset() dependency */
52 }
53
54 p = buf;
55 do {
56 *p++ = duk__format_digits[v % radix];
57 v /= radix;
58 } while (v != 0);
59
60 required = buf + fixed_length;
61 if (p < required && pad == (char) '0') {
62 /* Zero padding and we didn't reach maximum length: place
63 * negative sign at the last position. We can't get here
64 * with fixed_length == 0 so that required[-1] is safe.
65 *
66 * Technically we should only do this for 'neg_sign == 1',
67 * but it's OK to advance the pointer even when that's not
68 * the case.
69 */
70 p = required - 1;
71 }
72 if (neg_sign) {
73 *p++ = (char) '-';
74 }
75 if (p < required) {
76 p = required;
77 }
78
79 /* Now [buf,p[ contains the result in reverse; copy into place. */
80
81 while (p > buf) {
82 p--;
83 DUK__WRITE_CHAR(*p);
84 }
85
86 return off;
87 }
88
89 /* Parse a pointer. Must parse whatever is produced by '%p' in sprintf(). */
duk__parse_pointer(const char * str,void ** out)90 static int duk__parse_pointer(const char *str, void **out) {
91 const unsigned char *p;
92 unsigned char ch;
93 int count;
94 int limit;
95 long val; /* assume void * fits into long */
96
97 /* We only need to parse what our minimal printf() produces, so that
98 * we can check for a '0x' prefix, and assume all hex digits are
99 * lowercase.
100 */
101
102 p = (const unsigned char *) str;
103 if (p[0] != (unsigned char) '0' || p[1] != (unsigned char) 'x') {
104 return 0;
105 }
106 p += 2;
107
108 for (val = 0, count = 0, limit = sizeof(void *) * 2; count < limit; count++) {
109 ch = *p++;
110
111 val <<= 4;
112 if (ch >= (unsigned char) '0' && ch <= (unsigned char) '9') {
113 val += ch - (unsigned char) '0';
114 } else if (ch >= (unsigned char) 'a' && ch <= (unsigned char) 'f') {
115 val += ch - (unsigned char) 'a' + 0x0a;
116 } else {
117 return 0;
118 }
119 }
120
121 /* The input may end at a NUL or garbage may follow. As long as we
122 * parse the '%p' correctly, garbage is allowed to follow, and the
123 * JX pointer parsing also relies on that.
124 */
125
126 *out = (void *) val;
127 return 1;
128 }
129
130 /* Minimal vsnprintf() entry point. */
duk_minimal_vsnprintf(char * str,size_t size,const char * format,va_list ap)131 int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
132 size_t off = 0;
133 const char *p;
134 #if 0
135 const char *p_tmp;
136 const char *p_fmt_start;
137 #endif
138 char c;
139 char pad;
140 int fixed_length;
141 int is_long;
142
143 /* Assume str != NULL unless size == 0.
144 * Assume format != NULL.
145 */
146
147 p = format;
148 for (;;) {
149 c = *p++;
150 if (c == (char) 0) {
151 break;
152 }
153 if (c != (char) '%') {
154 DUK__WRITE_CHAR(c);
155 continue;
156 }
157
158 /* Start format sequence. Scan flags and format specifier. */
159
160 #if 0
161 p_fmt_start = p - 1;
162 #endif
163 is_long = 0;
164 pad = ' ';
165 fixed_length = 0;
166 for (;;) {
167 c = *p++;
168 if (c == (char) 'l') {
169 is_long = 1;
170 } else if (c == (char) '0') {
171 /* Only support pad character '0'. */
172 pad = '0';
173 } else if (c >= (char) '1' && c <= (char) '9') {
174 /* Only support fixed lengths 1-9. */
175 fixed_length = (int) (c - (char) '0');
176 } else if (c == (char) 'd') {
177 long v;
178 int neg_sign = 0;
179 if (is_long) {
180 v = va_arg(ap, long);
181 } else {
182 v = (long) va_arg(ap, int);
183 }
184 if (v < 0) {
185 neg_sign = 1;
186 v = -v;
187 }
188 off = duk__format_long(str, size, off, fixed_length, pad, 10, neg_sign, (unsigned long) v);
189 break;
190 } else if (c == (char) 'u') {
191 unsigned long v;
192 if (is_long) {
193 v = va_arg(ap, unsigned long);
194 } else {
195 v = (unsigned long) va_arg(ap, unsigned int);
196 }
197 off = duk__format_long(str, size, off, fixed_length, pad, 10, 0, v);
198 break;
199 } else if (c == (char) 'x') {
200 unsigned long v;
201 if (is_long) {
202 v = va_arg(ap, unsigned long);
203 } else {
204 v = (unsigned long) va_arg(ap, unsigned int);
205 }
206 off = duk__format_long(str, size, off, fixed_length, pad, 16, 0, v);
207 break;
208 } else if (c == (char) 'c') {
209 char v;
210 v = (char) va_arg(ap, int); /* intentionally not 'char' */
211 DUK__WRITE_CHAR(v);
212 break;
213 } else if (c == (char) 's') {
214 const char *v;
215 char c_tmp;
216 v = va_arg(ap, const char *);
217 if (v) {
218 for (;;) {
219 c_tmp = *v++;
220 if (c_tmp) {
221 DUK__WRITE_CHAR(c_tmp);
222 } else {
223 break;
224 }
225 }
226 }
227 break;
228 } else if (c == (char) 'p') {
229 /* Assume a void * can be represented by 'long'. This is not
230 * always the case. NULL pointer is printed out as 0x0000...
231 */
232 void *v;
233 v = va_arg(ap, void *);
234 DUK__WRITE_CHAR('0');
235 DUK__WRITE_CHAR('x');
236 off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v);
237 break;
238 } else {
239 /* Unrecognized, bail out early. We could also emit the format
240 * specifier verbatim, but it'd be a waste of footprint because
241 * this case should never happen in practice.
242 */
243 #if 0
244 DUK__WRITE_CHAR('!');
245 #endif
246 #if 0
247 for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) {
248 DUK__WRITE_CHAR(*p_tmp);
249 }
250 break;
251 #endif
252 goto finish;
253 }
254 }
255 }
256
257 finish:
258 if (off < size) {
259 str[off] = (char) 0; /* No increment for 'off', not counted in return value. */
260 } else if (size > 0) {
261 /* Forced termination. */
262 str[size - 1] = 0;
263 }
264
265 return (int) off;
266 }
267
268 /* Minimal snprintf() entry point. */
duk_minimal_snprintf(char * str,size_t size,const char * format,...)269 int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) {
270 va_list ap;
271 int ret;
272
273 va_start(ap, format);
274 ret = duk_minimal_vsnprintf(str, size, format, ap);
275 va_end(ap);
276
277 return ret;
278 }
279
280 /* Minimal sprintf() entry point. */
duk_minimal_sprintf(char * str,const char * format,...)281 int duk_minimal_sprintf(char *str, const char *format, ...) {
282 va_list ap;
283 int ret;
284
285 va_start(ap, format);
286 ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap);
287 va_end(ap);
288
289 return ret;
290 }
291
292 /* Minimal sscanf() entry point. */
duk_minimal_sscanf(const char * str,const char * format,...)293 int duk_minimal_sscanf(const char *str, const char *format, ...) {
294 va_list ap;
295 int ret;
296 void **out;
297
298 /* Only the exact "%p" format is supported. */
299 if (format[0] != (char) '%' ||
300 format[1] != (char) 'p' ||
301 format[2] != (char) 0) {
302 }
303
304 va_start(ap, format);
305 out = va_arg(ap, void **);
306 ret = duk__parse_pointer(str, out);
307 va_end(ap);
308
309 return ret;
310 }
311
312 #undef DUK__WRITE_CHAR
313