]>
Commit | Line | Data |
---|---|---|
1e59de90 TL |
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | */ | |
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 */ |