1 /*
2  *  Custom formatter for debug printing, allowing Duktape specific data
3  *  structures (such as tagged values and heap objects) to be printed with
4  *  a nice format string.  Because debug printing should not affect execution
5  *  state, formatting here must be independent of execution (see implications
6  *  below) and must not allocate memory.
7  *
8  *  Custom format tags begin with a '%!' to safely distinguish them from
9  *  standard format tags.  The following conversions are supported:
10  *
11  *     %!T    tagged value (duk_tval *)
12  *     %!O    heap object (duk_heaphdr *)
13  *     %!I    decoded bytecode instruction
14  *     %!C    bytecode instruction opcode name (arg is long)
15  *
16  *  Everything is serialized in a JSON-like manner.  The default depth is one
17  *  level, internal prototype is not followed, and internal properties are not
18  *  serialized.  The following modifiers change this behavior:
19  *
20  *     @      print pointers
21  *     #      print binary representations (where applicable)
22  *     d      deep traversal of own properties (not prototype)
23  *     p      follow prototype chain (useless without 'd')
24  *     i      include internal properties (other than prototype)
25  *     x      hexdump buffers
26  *     h      heavy formatting
27  *
28  *  For instance, the following serializes objects recursively, but does not
29  *  follow the prototype chain nor print internal properties: "%!dO".
30  *
31  *  Notes:
32  *
33  *    * Standard snprintf return value semantics seem to vary.  This
34  *      implementation returns the number of bytes it actually wrote
35  *      (excluding the null terminator).  If retval == buffer size,
36  *      output was truncated (except for corner cases).
37  *
38  *    * Output format is intentionally different from Ecmascript
39  *      formatting requirements, as formatting here serves debugging
40  *      of internals.
41  *
42  *    * Depth checking (and updating) is done in each type printer
43  *      separately, to allow them to call each other freely.
44  *
45  *    * Some pathological structures might take ages to print (e.g.
46  *      self recursion with 100 properties pointing to the object
47  *      itself).  To guard against these, each printer also checks
48  *      whether the output buffer is full; if so, early exit.
49  *
50  *    * Reference loops are detected using a loop stack.
51  */
52 
53 #include "duk_internal.h"
54 
55 #ifdef DUK_USE_DEBUG
56 
57 #include <stdio.h>
58 #include <stdarg.h>
59 #include <string.h>
60 
61 /* list of conversion specifiers that terminate a format tag;
62  * this is unfortunately guesswork.
63  */
64 #define DUK__ALLOWED_STANDARD_SPECIFIERS  "diouxXeEfFgGaAcsCSpnm"
65 
66 /* maximum length of standard format tag that we support */
67 #define DUK__MAX_FORMAT_TAG_LENGTH  32
68 
69 /* heapobj recursion depth when deep printing is selected */
70 #define DUK__DEEP_DEPTH_LIMIT  8
71 
72 /* maximum recursion depth for loop detection stacks */
73 #define DUK__LOOP_STACK_DEPTH  256
74 
75 /* must match bytecode defines now; build autogenerate? */
76 DUK_LOCAL const char *duk__bc_optab[64] = {
77 	"LDREG",    "STREG",    "LDCONST",  "LDINT",    "LDINTX",   "MPUTOBJ",  "MPUTOBJI", "MPUTARR",  "MPUTARRI", "NEW",
78 	"NEWI",     "REGEXP",   "CSREG",    "CSREGI",   "GETVAR",   "PUTVAR",   "DECLVAR",  "DELVAR",   "CSVAR",    "CSVARI",
79 	"CLOSURE",  "GETPROP",  "PUTPROP",  "DELPROP",  "CSPROP",   "CSPROPI",  "ADD",      "SUB",      "MUL",      "DIV",
80 	"MOD",      "BAND",     "BOR",      "BXOR",     "BASL",     "BLSR",     "BASR",     "EQ",       "NEQ",      "SEQ",
81 	"SNEQ",     "GT",       "GE",       "LT",       "LE",       "IF",       "JUMP",     "RETURN",   "CALL",     "CALLI",
82 	"TRYCATCH", "EXTRA",    "PREINCR",  "PREDECR",  "POSTINCR", "POSTDECR", "PREINCV",  "PREDECV",  "POSTINCV", "POSTDECV",
83 	"PREINCP",  "PREDECP",  "POSTINCP", "POSTDECP"
84 };
85 
86 DUK_LOCAL const char *duk__bc_extraoptab[256] = {
87 	"NOP", "INVALID", "LDTHIS", "LDUNDEF", "LDNULL", "LDTRUE", "LDFALSE", "NEWOBJ", "NEWARR", "SETALEN",
88 	"TYPEOF", "TYPEOFID", "INITENUM", "NEXTENUM", "INITSET", "INITSETI", "INITGET", "INITGETI", "ENDTRY", "ENDCATCH",
89 	"ENDFIN", "THROW", "INVLHS", "UNM", "UNP", "DEBUGGER", "BREAK", "CONTINUE", "BNOT", "LNOT",
90 	"INSTOF", "IN", "LABEL", "ENDLABEL", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
91 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
92 
93 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
94 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
95 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
96 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
97 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
98 
99 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
100 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
101 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
102 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
103 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
104 
105 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
106 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
107 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
108 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
109 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
110 
111 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
112 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
113 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
114 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
115 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
116 
117 	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX"
118 };
119 
120 typedef struct duk__dprint_state duk__dprint_state;
121 struct duk__dprint_state {
122 	duk_fixedbuffer *fb;
123 
124 	/* loop_stack_index could be perhaps be replaced by 'depth', but it's nice
125 	 * to not couple these two mechanisms unnecessarily.
126 	 */
127 	duk_hobject *loop_stack[DUK__LOOP_STACK_DEPTH];
128 	duk_int_t loop_stack_index;
129 	duk_int_t loop_stack_limit;
130 
131 	duk_int_t depth;
132 	duk_int_t depth_limit;
133 
134 	duk_bool_t pointer;
135 	duk_bool_t heavy;
136 	duk_bool_t binary;
137 	duk_bool_t follow_proto;
138 	duk_bool_t internal;
139 	duk_bool_t hexdump;
140 };
141 
142 /* helpers */
143 DUK_LOCAL_DECL void duk__print_hstring(duk__dprint_state *st, duk_hstring *k, duk_bool_t quotes);
144 DUK_LOCAL_DECL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h);
145 DUK_LOCAL_DECL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h);
146 DUK_LOCAL_DECL void duk__print_tval(duk__dprint_state *st, duk_tval *tv);
147 DUK_LOCAL_DECL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins);
148 DUK_LOCAL_DECL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
149 DUK_LOCAL_DECL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
150 DUK_LOCAL_DECL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h);
151 
duk__print_shared_heaphdr(duk__dprint_state * st,duk_heaphdr * h)152 DUK_LOCAL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
153 	duk_fixedbuffer *fb = st->fb;
154 
155 	if (st->heavy) {
156 		duk_fb_sprintf(fb, "(%p)", (void *) h);
157 	}
158 
159 	if (!h) {
160 		return;
161 	}
162 
163 	if (st->binary) {
164 		duk_size_t i;
165 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
166 		for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
167 			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
168 		}
169 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
170 	}
171 
172 #ifdef DUK_USE_REFERENCE_COUNTING  /* currently implicitly also DUK_USE_DOUBLE_LINKED_HEAP */
173 	if (st->heavy) {
174 		duk_fb_sprintf(fb, "[h_next=%p,h_prev=%p,h_refcount=%lu,h_flags=%08lx,type=%ld,"
175 		               "reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
176 		               (void *) DUK_HEAPHDR_GET_NEXT(NULL, h),
177 		               (void *) DUK_HEAPHDR_GET_PREV(NULL, h),
178 		               (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(h),
179 		               (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
180 		               (long) DUK_HEAPHDR_GET_TYPE(h),
181 		               (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
182 		               (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
183 		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
184 		               (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
185 	}
186 #else
187 	if (st->heavy) {
188 		duk_fb_sprintf(fb, "[h_next=%p,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
189 		               (void *) DUK_HEAPHDR_GET_NEXT(NULL, h),
190 		               (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
191 		               (long) DUK_HEAPHDR_GET_TYPE(h),
192 		               (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
193 		               (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
194 		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
195 		               (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
196 	}
197 #endif
198 }
199 
duk__print_shared_heaphdr_string(duk__dprint_state * st,duk_heaphdr_string * h)200 DUK_LOCAL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h) {
201 	duk_fixedbuffer *fb = st->fb;
202 
203 	if (st->heavy) {
204 		duk_fb_sprintf(fb, "(%p)", (void *) h);
205 	}
206 
207 	if (!h) {
208 		return;
209 	}
210 
211 	if (st->binary) {
212 		duk_size_t i;
213 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
214 		for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
215 			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
216 		}
217 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
218 	}
219 
220 #ifdef DUK_USE_REFERENCE_COUNTING
221 	if (st->heavy) {
222 		duk_fb_sprintf(fb, "[h_refcount=%lu,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
223 		               (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h),
224 		               (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
225 		               (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
226 		               (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
227 		               (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
228 		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
229 		               (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
230 	}
231 #else
232 	if (st->heavy) {
233 		duk_fb_sprintf(fb, "[h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
234 		               (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
235 		               (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
236 		               (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
237 		               (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
238 		               (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
239 		               (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
240 	}
241 #endif
242 }
243 
duk__print_hstring(duk__dprint_state * st,duk_hstring * h,duk_bool_t quotes)244 DUK_LOCAL void duk__print_hstring(duk__dprint_state *st, duk_hstring *h, duk_bool_t quotes) {
245 	duk_fixedbuffer *fb = st->fb;
246 	const duk_uint8_t *p;
247 	const duk_uint8_t *p_end;
248 
249 	/* terminal type: no depth check */
250 
251 	if (duk_fb_is_full(fb)) {
252 		return;
253 	}
254 
255 	duk__print_shared_heaphdr_string(st, &h->hdr);
256 
257 	if (!h) {
258 		duk_fb_put_cstring(fb, "NULL");
259 		return;
260 	}
261 
262 	p = DUK_HSTRING_GET_DATA(h);
263 	p_end = p + DUK_HSTRING_GET_BYTELEN(h);
264 
265 	if (p_end > p && p[0] == DUK_ASC_UNDERSCORE) {
266 		/* if property key begins with underscore, encode it with
267 		 * forced quotes (e.g. "_Foo") to distinguish it from encoded
268 		 * internal properties (e.g. \xffBar -> _Bar).
269 		 */
270 		quotes = 1;
271 	}
272 
273 	if (quotes) {
274 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
275 	}
276 	while (p < p_end) {
277 		duk_uint8_t ch = *p++;
278 
279 		/* two special escapes: '\' and '"', other printables as is */
280 		if (ch == '\\') {
281 			duk_fb_sprintf(fb, "\\\\");
282 		} else if (ch == '"') {
283 			duk_fb_sprintf(fb, "\\\"");
284 		} else if (ch >= 0x20 && ch <= 0x7e) {
285 			duk_fb_put_byte(fb, ch);
286 		} else if (ch == 0xff && !quotes) {
287 			/* encode \xffBar as _Bar if no quotes are applied, this is for
288 			 * readable internal keys.
289 			 */
290 			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_UNDERSCORE);
291 		} else {
292 			duk_fb_sprintf(fb, "\\x%02lx", (unsigned long) ch);
293 		}
294 	}
295 	if (quotes) {
296 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
297 	}
298 #ifdef DUK_USE_REFERENCE_COUNTING
299 	/* XXX: limit to quoted strings only, to save keys from being cluttered? */
300 	duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
301 #endif
302 }
303 
304 #ifdef DUK__COMMA
305 #undef DUK__COMMA
306 #endif
307 #define DUK__COMMA()  do { \
308 		if (first) { \
309 			first = 0; \
310 		} else { \
311 			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA); \
312 		} \
313 	} while (0)
314 
duk__print_hobject(duk__dprint_state * st,duk_hobject * h)315 DUK_LOCAL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h) {
316 	duk_fixedbuffer *fb = st->fb;
317 	duk_uint_fast32_t i;
318 	duk_tval *tv;
319 	duk_hstring *key;
320 	duk_bool_t first = 1;
321 	const char *brace1 = "{";
322 	const char *brace2 = "}";
323 	duk_bool_t pushed_loopstack = 0;
324 
325 	if (duk_fb_is_full(fb)) {
326 		return;
327 	}
328 
329 	duk__print_shared_heaphdr(st, &h->hdr);
330 
331 	if (h && DUK_HOBJECT_HAS_ARRAY_PART(h)) {
332 		brace1 = "[";
333 		brace2 = "]";
334 	}
335 
336 	if (!h) {
337 		duk_fb_put_cstring(fb, "NULL");
338 		goto finished;
339 	}
340 
341 	if (st->depth >= st->depth_limit) {
342 		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
343 			duk_fb_sprintf(fb, "%sobject/compiledfunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
344 		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
345 			duk_fb_sprintf(fb, "%sobject/nativefunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
346 		} else if (DUK_HOBJECT_IS_THREAD(h)) {
347 			duk_fb_sprintf(fb, "%sobject/thread %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
348 		} else {
349 			duk_fb_sprintf(fb, "%sobject %p%s", (const char *) brace1, (void *) h, (const char *) brace2);  /* may be NULL */
350 		}
351 		return;
352 	}
353 
354 	for (i = 0; i < (duk_uint_fast32_t) st->loop_stack_index; i++) {
355 		if (st->loop_stack[i] == h) {
356 			duk_fb_sprintf(fb, "%sLOOP:%p%s", (const char *) brace1, (void *) h, (const char *) brace2);
357 			return;
358 		}
359 	}
360 
361 	/* after this, return paths should 'goto finished' for decrement */
362 	st->depth++;
363 
364 	if (st->loop_stack_index >= st->loop_stack_limit) {
365 		duk_fb_sprintf(fb, "%sOUT-OF-LOOP-STACK%s", (const char *) brace1, (const char *) brace2);
366 		goto finished;
367 	}
368 	st->loop_stack[st->loop_stack_index++] = h;
369 	pushed_loopstack = 1;
370 
371 	/*
372 	 *  Notation: double underscore used for internal properties which are not
373 	 *  stored in the property allocation (e.g. '__valstack').
374 	 */
375 
376 	duk_fb_put_cstring(fb, brace1);
377 
378 	if (DUK_HOBJECT_GET_PROPS(NULL, h)) {
379 		duk_uint32_t a_limit;
380 
381 		a_limit = DUK_HOBJECT_GET_ASIZE(h);
382 		if (st->internal) {
383 			/* dump all allocated entries, unused entries print as 'unused',
384 			 * note that these may extend beyond current 'length' and look
385 			 * a bit funny.
386 			 */
387 		} else {
388 			/* leave out trailing 'unused' elements */
389 			while (a_limit > 0) {
390 				tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, a_limit - 1);
391 				if (!DUK_TVAL_IS_UNUSED(tv)) {
392 					break;
393 				}
394 				a_limit--;
395 			}
396 		}
397 
398 		for (i = 0; i < a_limit; i++) {
399 			tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, i);
400 			DUK__COMMA();
401 			duk__print_tval(st, tv);
402 		}
403 		for (i = 0; i < DUK_HOBJECT_GET_ENEXT(h); i++) {
404 			key = DUK_HOBJECT_E_GET_KEY(NULL, h, i);
405 			if (!key) {
406 				continue;
407 			}
408 			if (!st->internal &&
409 			    DUK_HSTRING_GET_BYTELEN(key) > 0 &&
410 			    DUK_HSTRING_GET_DATA(key)[0] == 0xff) {
411 				/* XXX: use DUK_HSTRING_FLAG_INTERNAL? */
412 				continue;
413 			}
414 			DUK__COMMA();
415 			duk__print_hstring(st, key, 0);
416 			duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COLON);
417 			if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(NULL, h, i)) {
418 				duk_fb_sprintf(fb, "[get:%p,set:%p]",
419 				               (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.get,
420 				               (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.set);
421 			} else {
422 				tv = &DUK_HOBJECT_E_GET_VALUE(NULL, h, i).v;
423 				duk__print_tval(st, tv);
424 			}
425 			if (st->heavy) {
426 				duk_fb_sprintf(fb, "<%02lx>", (unsigned long) DUK_HOBJECT_E_GET_FLAGS(NULL, h, i));
427 			}
428 		}
429 	}
430 	if (st->internal) {
431 		if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) {
432 			DUK__COMMA(); duk_fb_sprintf(fb, "__extensible:true");
433 		} else {
434 			;
435 		}
436 		if (DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) {
437 			DUK__COMMA(); duk_fb_sprintf(fb, "__constructable:true");
438 		} else {
439 			;
440 		}
441 		if (DUK_HOBJECT_HAS_BOUND(h)) {
442 			DUK__COMMA(); duk_fb_sprintf(fb, "__bound:true");
443 		} else {
444 			;
445 		}
446 		if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)) {
447 			DUK__COMMA(); duk_fb_sprintf(fb, "__compiledfunction:true");
448 		} else {
449 			;
450 		}
451 		if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) {
452 			DUK__COMMA(); duk_fb_sprintf(fb, "__nativefunction:true");
453 		} else {
454 			;
455 		}
456 		if (DUK_HOBJECT_HAS_THREAD(h)) {
457 			DUK__COMMA(); duk_fb_sprintf(fb, "__thread:true");
458 		} else {
459 			;
460 		}
461 		if (DUK_HOBJECT_HAS_ARRAY_PART(h)) {
462 			DUK__COMMA(); duk_fb_sprintf(fb, "__array_part:true");
463 		} else {
464 			;
465 		}
466 		if (DUK_HOBJECT_HAS_STRICT(h)) {
467 			DUK__COMMA(); duk_fb_sprintf(fb, "__strict:true");
468 		} else {
469 			;
470 		}
471 		if (DUK_HOBJECT_HAS_NEWENV(h)) {
472 			DUK__COMMA(); duk_fb_sprintf(fb, "__newenv:true");
473 		} else {
474 			;
475 		}
476 		if (DUK_HOBJECT_HAS_NAMEBINDING(h)) {
477 			DUK__COMMA(); duk_fb_sprintf(fb, "__namebinding:true");
478 		} else {
479 			;
480 		}
481 		if (DUK_HOBJECT_HAS_CREATEARGS(h)) {
482 			DUK__COMMA(); duk_fb_sprintf(fb, "__createargs:true");
483 		} else {
484 			;
485 		}
486 		if (DUK_HOBJECT_HAS_ENVRECCLOSED(h)) {
487 			DUK__COMMA(); duk_fb_sprintf(fb, "__envrecclosed:true");
488 		} else {
489 			;
490 		}
491 		if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) {
492 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_array:true");
493 		} else {
494 			;
495 		}
496 		if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)) {
497 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_stringobj:true");
498 		} else {
499 			;
500 		}
501 		if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)) {
502 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_arguments:true");
503 		} else {
504 			;
505 		}
506 		if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(h)) {
507 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_dukfunc:true");
508 		} else {
509 			;
510 		}
511 		if (DUK_HOBJECT_IS_BUFFEROBJECT(h)) {
512 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_bufferobj:true");
513 		} else {
514 			;
515 		}
516 		if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h)) {
517 			DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_proxyobj:true");
518 		} else {
519 			;
520 		}
521 	}
522 	if (st->internal && DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
523 		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
524 		DUK__COMMA(); duk_fb_put_cstring(fb, "__data:");
525 		duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(NULL, f));
526 		DUK__COMMA(); duk_fb_sprintf(fb, "__nregs:%ld", (long) f->nregs);
527 		DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
528 #if defined(DUK_USE_DEBUGGER_SUPPORT)
529 		DUK__COMMA(); duk_fb_sprintf(fb, "__start_line:%ld", (long) f->start_line);
530 		DUK__COMMA(); duk_fb_sprintf(fb, "__end_line:%ld", (long) f->end_line);
531 #endif
532 		DUK__COMMA(); duk_fb_put_cstring(fb, "__data:");
533 		duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(NULL, f));
534 	} else if (st->internal && DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
535 		duk_hnativefunction *f = (duk_hnativefunction *) h;
536 		DUK__COMMA(); duk_fb_sprintf(fb, "__func:");
537 		duk_fb_put_funcptr(fb, (duk_uint8_t *) &f->func, sizeof(f->func));
538 		DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
539 		DUK__COMMA(); duk_fb_sprintf(fb, "__magic:%ld", (long) f->magic);
540 	} else if (st->internal && DUK_HOBJECT_IS_BUFFEROBJECT(h)) {
541 		duk_hbufferobject *b = (duk_hbufferobject *) h;
542 		DUK__COMMA(); duk_fb_sprintf(fb, "__buf:");
543 		duk__print_hbuffer(st, (duk_hbuffer *) b->buf);
544 		DUK__COMMA(); duk_fb_sprintf(fb, "__offset:%ld", (long) b->offset);
545 		DUK__COMMA(); duk_fb_sprintf(fb, "__length:%ld", (long) b->length);
546 		DUK__COMMA(); duk_fb_sprintf(fb, "__shift:%ld", (long) b->shift);
547 		DUK__COMMA(); duk_fb_sprintf(fb, "__elemtype:%ld", (long) b->elem_type);
548 	} else if (st->internal && DUK_HOBJECT_IS_THREAD(h)) {
549 		duk_hthread *t = (duk_hthread *) h;
550 		DUK__COMMA(); duk_fb_sprintf(fb, "__strict:%ld", (long) t->strict);
551 		DUK__COMMA(); duk_fb_sprintf(fb, "__state:%ld", (long) t->state);
552 		DUK__COMMA(); duk_fb_sprintf(fb, "__unused1:%ld", (long) t->unused1);
553 		DUK__COMMA(); duk_fb_sprintf(fb, "__unused2:%ld", (long) t->unused2);
554 		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_max:%ld", (long) t->valstack_max);
555 		DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_max:%ld", (long) t->callstack_max);
556 		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_max:%ld", (long) t->catchstack_max);
557 		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack:%p", (void *) t->valstack);
558 		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_end:%p/%ld", (void *) t->valstack_end, (long) (t->valstack_end - t->valstack));
559 		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_bottom:%p/%ld", (void *) t->valstack_bottom, (long) (t->valstack_bottom - t->valstack));
560 		DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_top:%p/%ld", (void *) t->valstack_top, (long) (t->valstack_top - t->valstack));
561 		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack:%p", (void *) t->catchstack);
562 		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_size:%ld", (long) t->catchstack_size);
563 		DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_top:%ld", (long) t->catchstack_top);
564 		DUK__COMMA(); duk_fb_sprintf(fb, "__resumer:"); duk__print_hobject(st, (duk_hobject *) t->resumer);
565 		/* XXX: print built-ins array? */
566 
567 	}
568 #ifdef DUK_USE_REFERENCE_COUNTING
569 	if (st->internal) {
570 		DUK__COMMA(); duk_fb_sprintf(fb, "__refcount:%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h));
571 	}
572 #endif
573 	if (st->internal) {
574 		DUK__COMMA(); duk_fb_sprintf(fb, "__class:%ld", (long) DUK_HOBJECT_GET_CLASS_NUMBER(h));
575 	}
576 
577 	DUK__COMMA(); duk_fb_sprintf(fb, "__heapptr:%p", (void *) h);  /* own pointer */
578 
579 	/* prototype should be last, for readability */
580 	if (DUK_HOBJECT_GET_PROTOTYPE(NULL, h)) {
581 		if (st->follow_proto) {
582 			DUK__COMMA(); duk_fb_put_cstring(fb, "__prototype:"); duk__print_hobject(st, DUK_HOBJECT_GET_PROTOTYPE(NULL, h));
583 		} else {
584 			DUK__COMMA(); duk_fb_sprintf(fb, "__prototype:%p", (void *) DUK_HOBJECT_GET_PROTOTYPE(NULL, h));
585 		}
586 	}
587 
588 	duk_fb_put_cstring(fb, brace2);
589 
590 #if defined(DUK_USE_HOBJECT_HASH_PART)
591 	if (st->heavy && DUK_HOBJECT_GET_HSIZE(h) > 0) {
592 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
593 		for (i = 0; i < DUK_HOBJECT_GET_HSIZE(h); i++) {
594 			duk_uint_t h_idx = DUK_HOBJECT_H_GET_INDEX(NULL, h, i);
595 			if (i > 0) {
596 				duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA);
597 			}
598 			if (h_idx == DUK_HOBJECT_HASHIDX_UNUSED) {
599 				duk_fb_sprintf(fb, "u");
600 			} else if (h_idx == DUK_HOBJECT_HASHIDX_DELETED) {
601 				duk_fb_sprintf(fb, "d");
602 			} else {
603 				duk_fb_sprintf(fb, "%ld", (long) h_idx);
604 			}
605 		}
606 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
607 	}
608 #endif
609 
610  finished:
611 	st->depth--;
612 	if (pushed_loopstack) {
613 		st->loop_stack_index--;
614 		st->loop_stack[st->loop_stack_index] = NULL;
615 	}
616 }
617 
618 #undef DUK__COMMA
619 
duk__print_hbuffer(duk__dprint_state * st,duk_hbuffer * h)620 DUK_LOCAL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h) {
621 	duk_fixedbuffer *fb = st->fb;
622 	duk_size_t i, n;
623 	duk_uint8_t *p;
624 
625 	if (duk_fb_is_full(fb)) {
626 		return;
627 	}
628 
629 	/* terminal type: no depth check */
630 
631 	if (!h) {
632 		duk_fb_put_cstring(fb, "NULL");
633 		return;
634 	}
635 
636 	if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
637 		if (DUK_HBUFFER_HAS_EXTERNAL(h)) {
638 			duk_hbuffer_external *g = (duk_hbuffer_external *) h;
639 			duk_fb_sprintf(fb, "buffer:external:%p:%ld",
640 			               (void *) DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(NULL, g),
641 			               (long) DUK_HBUFFER_EXTERNAL_GET_SIZE(g));
642 		} else {
643 			duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
644 			duk_fb_sprintf(fb, "buffer:dynamic:%p:%ld",
645 			               (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(NULL, g),
646 			               (long) DUK_HBUFFER_DYNAMIC_GET_SIZE(g));
647 		}
648 	} else {
649 		duk_fb_sprintf(fb, "buffer:fixed:%ld", (long) DUK_HBUFFER_GET_SIZE(h));
650 	}
651 
652 #ifdef DUK_USE_REFERENCE_COUNTING
653 	duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
654 #endif
655 
656 	if (st->hexdump) {
657 		duk_fb_sprintf(fb, "=[");
658 		n = DUK_HBUFFER_GET_SIZE(h);
659 		p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(NULL, h);
660 		for (i = 0; i < n; i++) {
661 			duk_fb_sprintf(fb, "%02lx", (unsigned long) p[i]);
662 		}
663 		duk_fb_sprintf(fb, "]");
664 	}
665 }
666 
duk__print_heaphdr(duk__dprint_state * st,duk_heaphdr * h)667 DUK_LOCAL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
668 	duk_fixedbuffer *fb = st->fb;
669 
670 	if (duk_fb_is_full(fb)) {
671 		return;
672 	}
673 
674 	if (!h) {
675 		duk_fb_put_cstring(fb, "NULL");
676 		return;
677 	}
678 
679 	switch (DUK_HEAPHDR_GET_TYPE(h)) {
680 	case DUK_HTYPE_STRING:
681 		duk__print_hstring(st, (duk_hstring *) h, 1);
682 		break;
683 	case DUK_HTYPE_OBJECT:
684 		duk__print_hobject(st, (duk_hobject *) h);
685 		break;
686 	case DUK_HTYPE_BUFFER:
687 		duk__print_hbuffer(st, (duk_hbuffer *) h);
688 		break;
689 	default:
690 		duk_fb_sprintf(fb, "[unknown htype %ld]", (long) DUK_HEAPHDR_GET_TYPE(h));
691 		break;
692 	}
693 }
694 
duk__print_tval(duk__dprint_state * st,duk_tval * tv)695 DUK_LOCAL void duk__print_tval(duk__dprint_state *st, duk_tval *tv) {
696 	duk_fixedbuffer *fb = st->fb;
697 
698 	if (duk_fb_is_full(fb)) {
699 		return;
700 	}
701 
702 	/* depth check is done when printing an actual type */
703 
704 	if (st->heavy) {
705 		duk_fb_sprintf(fb, "(%p)", (void *) tv);
706 	}
707 
708 	if (!tv) {
709 		duk_fb_put_cstring(fb, "NULL");
710 		return;
711 	}
712 
713 	if (st->binary) {
714 		duk_size_t i;
715 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
716 		for (i = 0; i < (duk_size_t) sizeof(*tv); i++) {
717 			duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)tv)[i]);
718 		}
719 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
720 	}
721 
722 	if (st->heavy) {
723 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
724 	}
725 	switch (DUK_TVAL_GET_TAG(tv)) {
726 	case DUK_TAG_UNDEFINED: {
727 		duk_fb_put_cstring(fb, "undefined");
728 		break;
729 	}
730 	case DUK_TAG_UNUSED: {
731 		duk_fb_put_cstring(fb, "unused");
732 		break;
733 	}
734 	case DUK_TAG_NULL: {
735 		duk_fb_put_cstring(fb, "null");
736 		break;
737 	}
738 	case DUK_TAG_BOOLEAN: {
739 		duk_fb_put_cstring(fb, DUK_TVAL_GET_BOOLEAN(tv) ? "true" : "false");
740 		break;
741 	}
742 	case DUK_TAG_STRING: {
743 		/* Note: string is a terminal heap object, so no depth check here */
744 		duk__print_hstring(st, DUK_TVAL_GET_STRING(tv), 1);
745 		break;
746 	}
747 	case DUK_TAG_OBJECT: {
748 		duk__print_hobject(st, DUK_TVAL_GET_OBJECT(tv));
749 		break;
750 	}
751 	case DUK_TAG_BUFFER: {
752 		duk__print_hbuffer(st, DUK_TVAL_GET_BUFFER(tv));
753 		break;
754 	}
755 	case DUK_TAG_POINTER: {
756 		duk_fb_sprintf(fb, "pointer:%p", (void *) DUK_TVAL_GET_POINTER(tv));
757 		break;
758 	}
759 	case DUK_TAG_LIGHTFUNC: {
760 		duk_c_function func;
761 		duk_small_uint_t lf_flags;
762 
763 		DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags);
764 		duk_fb_sprintf(fb, "lightfunc:");
765 		duk_fb_put_funcptr(fb, (duk_uint8_t *) &func, sizeof(func));
766 		duk_fb_sprintf(fb, ":%04lx", (long) lf_flags);
767 		break;
768 	}
769 #if defined(DUK_USE_FASTINT)
770 	case DUK_TAG_FASTINT:
771 #endif
772 	default: {
773 		/* IEEE double is approximately 16 decimal digits; print a couple extra */
774 		DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
775 		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
776 		duk_fb_sprintf(fb, "%.18g", (double) DUK_TVAL_GET_NUMBER(tv));
777 		break;
778 	}
779 	}
780 	if (st->heavy) {
781 		duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
782 	}
783 }
784 
duk__print_instr(duk__dprint_state * st,duk_instr_t ins)785 DUK_LOCAL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins) {
786 	duk_fixedbuffer *fb = st->fb;
787 	duk_small_int_t op;
788 	const char *op_name;
789 	const char *extraop_name;
790 
791 	op = (duk_small_int_t) DUK_DEC_OP(ins);
792 	op_name = duk__bc_optab[op];
793 
794 	/* XXX: option to fix opcode length so it lines up nicely */
795 
796 	if (op == DUK_OP_EXTRA) {
797 		extraop_name = duk__bc_extraoptab[DUK_DEC_A(ins)];
798 
799 		duk_fb_sprintf(fb, "%s %ld, %ld",
800 		               (const char *) extraop_name, (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
801 	} else if (op == DUK_OP_JUMP) {
802 		duk_int_t diff1 = DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;  /* from next pc */
803 		duk_int_t diff2 = diff1 + 1;                            /* from curr pc */
804 
805 		duk_fb_sprintf(fb, "%s %ld (to pc%c%ld)",
806 		               (const char *) op_name, (long) diff1,
807 		               (int) (diff2 >= 0 ? '+' : '-'),  /* char format: use int */
808 		               (long) (diff2 >= 0 ? diff2 : -diff2));
809 	} else {
810 		duk_fb_sprintf(fb, "%s %ld, %ld, %ld",
811 		               (const char *) op_name, (long) DUK_DEC_A(ins),
812 		               (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
813 	}
814 }
815 
duk__print_opcode(duk__dprint_state * st,duk_small_int_t opcode)816 DUK_LOCAL void duk__print_opcode(duk__dprint_state *st, duk_small_int_t opcode) {
817 	duk_fixedbuffer *fb = st->fb;
818 
819 	if (opcode < DUK_BC_OP_MIN || opcode > DUK_BC_OP_MAX) {
820 		duk_fb_sprintf(fb, "?(%ld)", (long) opcode);
821 	} else {
822 		duk_fb_sprintf(fb, "%s", (const char *) duk__bc_optab[opcode]);
823 	}
824 }
825 
duk_debug_vsnprintf(char * str,duk_size_t size,const char * format,va_list ap)826 DUK_INTERNAL duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap) {
827 	duk_fixedbuffer fb;
828 	const char *p = format;
829 	const char *p_end = p + DUK_STRLEN(format);
830 	duk_int_t retval;
831 
832 	DUK_MEMZERO(&fb, sizeof(fb));
833 	fb.buffer = (duk_uint8_t *) str;
834 	fb.length = size;
835 	fb.offset = 0;
836 	fb.truncated = 0;
837 
838 	while (p < p_end) {
839 		char ch = *p++;
840 		const char *p_begfmt = NULL;
841 		duk_bool_t got_exclamation = 0;
842 		duk_bool_t got_long = 0;  /* %lf, %ld etc */
843 		duk__dprint_state st;
844 
845 		if (ch != DUK_ASC_PERCENT) {
846 			duk_fb_put_byte(&fb, (duk_uint8_t) ch);
847 			continue;
848 		}
849 
850 		/*
851 		 *  Format tag parsing.  Since we don't understand all the
852 		 *  possible format tags allowed, we just scan for a terminating
853 		 *  specifier and keep track of relevant modifiers that we do
854 		 *  understand.  See man 3 printf.
855 		 */
856 
857 		DUK_MEMZERO(&st, sizeof(st));
858 		st.fb = &fb;
859 		st.depth = 0;
860 		st.depth_limit = 1;
861 		st.loop_stack_index = 0;
862 		st.loop_stack_limit = DUK__LOOP_STACK_DEPTH;
863 
864 		p_begfmt = p - 1;
865 		while (p < p_end) {
866 			ch = *p++;
867 
868 			if (ch == DUK_ASC_STAR) {
869 				/* unsupported: would consume multiple args */
870 				goto error;
871 			} else if (ch == DUK_ASC_PERCENT) {
872 				duk_fb_put_byte(&fb, (duk_uint8_t) DUK_ASC_PERCENT);
873 				break;
874 			} else if (ch == DUK_ASC_EXCLAMATION) {
875 				got_exclamation = 1;
876 			} else if (!got_exclamation && ch == DUK_ASC_LC_L) {
877 				got_long = 1;
878 			} else if (got_exclamation && ch == DUK_ASC_LC_D) {
879 				st.depth_limit = DUK__DEEP_DEPTH_LIMIT;
880 			} else if (got_exclamation && ch == DUK_ASC_LC_P) {
881 				st.follow_proto = 1;
882 			} else if (got_exclamation && ch == DUK_ASC_LC_I) {
883 				st.internal = 1;
884 			} else if (got_exclamation && ch == DUK_ASC_LC_X) {
885 				st.hexdump = 1;
886 			} else if (got_exclamation && ch == DUK_ASC_LC_H) {
887 				st.heavy = 1;
888 			} else if (got_exclamation && ch == DUK_ASC_ATSIGN) {
889 				st.pointer = 1;
890 			} else if (got_exclamation && ch == DUK_ASC_HASH) {
891 				st.binary = 1;
892 			} else if (got_exclamation && ch == DUK_ASC_UC_T) {
893 				duk_tval *t = va_arg(ap, duk_tval *);
894 				if (st.pointer && !st.heavy) {
895 					duk_fb_sprintf(&fb, "(%p)", (void *) t);
896 				}
897 				duk__print_tval(&st, t);
898 				break;
899 			} else if (got_exclamation && ch == DUK_ASC_UC_O) {
900 				duk_heaphdr *t = va_arg(ap, duk_heaphdr *);
901 				if (st.pointer && !st.heavy) {
902 					duk_fb_sprintf(&fb, "(%p)", (void *) t);
903 				}
904 				duk__print_heaphdr(&st, t);
905 				break;
906 			} else if (got_exclamation && ch == DUK_ASC_UC_I) {
907 				duk_instr_t t = va_arg(ap, duk_instr_t);
908 				duk__print_instr(&st, t);
909 				break;
910 			} else if (got_exclamation && ch == DUK_ASC_UC_C) {
911 				long t = va_arg(ap, long);
912 				duk__print_opcode(&st, (duk_small_int_t) t);
913 				break;
914 			} else if (!got_exclamation && strchr(DUK__ALLOWED_STANDARD_SPECIFIERS, (int) ch)) {
915 				char fmtbuf[DUK__MAX_FORMAT_TAG_LENGTH];
916 				duk_size_t fmtlen;
917 
918 				DUK_ASSERT(p >= p_begfmt);
919 				fmtlen = (duk_size_t) (p - p_begfmt);
920 				if (fmtlen >= sizeof(fmtbuf)) {
921 					/* format is too large, abort */
922 					goto error;
923 				}
924 				DUK_MEMZERO(fmtbuf, sizeof(fmtbuf));
925 				DUK_MEMCPY(fmtbuf, p_begfmt, fmtlen);
926 
927 				/* assume exactly 1 arg, which is why '*' is forbidden; arg size still
928 				 * depends on type though.
929 				 */
930 
931 				if (ch == DUK_ASC_LC_F || ch == DUK_ASC_LC_G || ch == DUK_ASC_LC_E) {
932 					/* %f and %lf both consume a 'long' */
933 					double arg = va_arg(ap, double);
934 					duk_fb_sprintf(&fb, fmtbuf, arg);
935 				} else if (ch == DUK_ASC_LC_D && got_long) {
936 					/* %ld */
937 					long arg = va_arg(ap, long);
938 					duk_fb_sprintf(&fb, fmtbuf, arg);
939 				} else if (ch == DUK_ASC_LC_D) {
940 					/* %d; only 16 bits are guaranteed */
941 					int arg = va_arg(ap, int);
942 					duk_fb_sprintf(&fb, fmtbuf, arg);
943 				} else if (ch == DUK_ASC_LC_U && got_long) {
944 					/* %lu */
945 					unsigned long arg = va_arg(ap, unsigned long);
946 					duk_fb_sprintf(&fb, fmtbuf, arg);
947 				} else if (ch == DUK_ASC_LC_U) {
948 					/* %u; only 16 bits are guaranteed */
949 					unsigned int arg = va_arg(ap, unsigned int);
950 					duk_fb_sprintf(&fb, fmtbuf, arg);
951 				} else if (ch == DUK_ASC_LC_X && got_long) {
952 					/* %lx */
953 					unsigned long arg = va_arg(ap, unsigned long);
954 					duk_fb_sprintf(&fb, fmtbuf, arg);
955 				} else if (ch == DUK_ASC_LC_X) {
956 					/* %x; only 16 bits are guaranteed */
957 					unsigned int arg = va_arg(ap, unsigned int);
958 					duk_fb_sprintf(&fb, fmtbuf, arg);
959 				} else if (ch == DUK_ASC_LC_S) {
960 					/* %s */
961 					const char *arg = va_arg(ap, const char *);
962 					if (arg == NULL) {
963 						/* '%s' and NULL is not portable, so special case
964 						 * it for debug printing.
965 						 */
966 						duk_fb_sprintf(&fb, "NULL");
967 					} else {
968 						duk_fb_sprintf(&fb, fmtbuf, arg);
969 					}
970 				} else if (ch == DUK_ASC_LC_P) {
971 					/* %p */
972 					void *arg = va_arg(ap, void *);
973 					if (arg == NULL) {
974 						/* '%p' and NULL is portable, but special case it
975 						 * anyway to get a standard NULL marker in logs.
976 						 */
977 						duk_fb_sprintf(&fb, "NULL");
978 					} else {
979 						duk_fb_sprintf(&fb, fmtbuf, arg);
980 					}
981 				} else if (ch == DUK_ASC_LC_C) {
982 					/* '%c', passed concretely as int */
983 					int arg = va_arg(ap, int);
984 					duk_fb_sprintf(&fb, fmtbuf, arg);
985 				} else {
986 					/* Should not happen. */
987 					duk_fb_sprintf(&fb, "INVALID-FORMAT(%s)", (const char *) fmtbuf);
988 				}
989 				break;
990 			} else {
991 				/* ignore */
992 			}
993 		}
994 	}
995 	goto done;
996 
997  error:
998 	duk_fb_put_cstring(&fb, "FMTERR");
999 	/* fall through */
1000 
1001  done:
1002 	retval = (duk_int_t) fb.offset;
1003 	duk_fb_put_byte(&fb, (duk_uint8_t) 0);
1004 
1005 	/* return total chars written excluding terminator */
1006 	return retval;
1007 }
1008 
1009 #if 0  /*unused*/
1010 DUK_INTERNAL duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...) {
1011 	duk_int_t retval;
1012 	va_list ap;
1013 	va_start(ap, format);
1014 	retval = duk_debug_vsnprintf(str, size, format, ap);
1015 	va_end(ap);
1016 	return retval;
1017 }
1018 #endif
1019 
1020 /* Formatting function pointers is tricky: there is no standard pointer for
1021  * function pointers and the size of a function pointer may depend on the
1022  * specific pointer type.  This helper formats a function pointer based on
1023  * its memory layout to get something useful on most platforms.
1024  */
duk_debug_format_funcptr(char * buf,duk_size_t buf_size,duk_uint8_t * fptr,duk_size_t fptr_size)1025 DUK_INTERNAL void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size) {
1026 	duk_size_t i;
1027 	duk_uint8_t *p = (duk_uint8_t *) buf;
1028 	duk_uint8_t *p_end = (duk_uint8_t *) (buf + buf_size - 1);
1029 
1030 	DUK_MEMZERO(buf, buf_size);
1031 
1032 	for (i = 0; i < fptr_size; i++) {
1033 		duk_int_t left = (duk_int_t) (p_end - p);
1034 		duk_uint8_t ch;
1035 		if (left <= 0) {
1036 			break;
1037 		}
1038 
1039 		/* Quite approximate but should be useful for little and big endian. */
1040 #ifdef DUK_USE_INTEGER_BE
1041 		ch = fptr[i];
1042 #else
1043 		ch = fptr[fptr_size - 1 - i];
1044 #endif
1045 		p += DUK_SNPRINTF((char *) p, left, "%02lx", (unsigned long) ch);
1046 	}
1047 }
1048 
1049 #endif  /* DUK_USE_DEBUG */
1050