]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_debug_vsnprintf.c
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / jaegertracing / opentelemetry-cpp / third_party / prometheus-cpp / 3rdparty / civetweb / src / third_party / duktape-1.8.0 / src-separate / duk_debug_vsnprintf.c
diff --git a/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_debug_vsnprintf.c b/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_debug_vsnprintf.c
new file mode 100644 (file)
index 0000000..50881c5
--- /dev/null
@@ -0,0 +1,1049 @@
+/*
+ *  Custom formatter for debug printing, allowing Duktape specific data
+ *  structures (such as tagged values and heap objects) to be printed with
+ *  a nice format string.  Because debug printing should not affect execution
+ *  state, formatting here must be independent of execution (see implications
+ *  below) and must not allocate memory.
+ *
+ *  Custom format tags begin with a '%!' to safely distinguish them from
+ *  standard format tags.  The following conversions are supported:
+ *
+ *     %!T    tagged value (duk_tval *)
+ *     %!O    heap object (duk_heaphdr *)
+ *     %!I    decoded bytecode instruction
+ *     %!C    bytecode instruction opcode name (arg is long)
+ *
+ *  Everything is serialized in a JSON-like manner.  The default depth is one
+ *  level, internal prototype is not followed, and internal properties are not
+ *  serialized.  The following modifiers change this behavior:
+ *
+ *     @      print pointers
+ *     #      print binary representations (where applicable)
+ *     d      deep traversal of own properties (not prototype)
+ *     p      follow prototype chain (useless without 'd')
+ *     i      include internal properties (other than prototype)
+ *     x      hexdump buffers
+ *     h      heavy formatting
+ *
+ *  For instance, the following serializes objects recursively, but does not
+ *  follow the prototype chain nor print internal properties: "%!dO".
+ *
+ *  Notes:
+ *
+ *    * Standard snprintf return value semantics seem to vary.  This
+ *      implementation returns the number of bytes it actually wrote
+ *      (excluding the null terminator).  If retval == buffer size,
+ *      output was truncated (except for corner cases).
+ *
+ *    * Output format is intentionally different from Ecmascript
+ *      formatting requirements, as formatting here serves debugging
+ *      of internals.
+ *
+ *    * Depth checking (and updating) is done in each type printer
+ *      separately, to allow them to call each other freely.
+ *
+ *    * Some pathological structures might take ages to print (e.g.
+ *      self recursion with 100 properties pointing to the object
+ *      itself).  To guard against these, each printer also checks
+ *      whether the output buffer is full; if so, early exit.
+ *
+ *    * Reference loops are detected using a loop stack.
+ */
+
+#include "duk_internal.h"
+
+#ifdef DUK_USE_DEBUG
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* list of conversion specifiers that terminate a format tag;
+ * this is unfortunately guesswork.
+ */
+#define DUK__ALLOWED_STANDARD_SPECIFIERS  "diouxXeEfFgGaAcsCSpnm"
+
+/* maximum length of standard format tag that we support */
+#define DUK__MAX_FORMAT_TAG_LENGTH  32
+
+/* heapobj recursion depth when deep printing is selected */
+#define DUK__DEEP_DEPTH_LIMIT  8
+
+/* maximum recursion depth for loop detection stacks */
+#define DUK__LOOP_STACK_DEPTH  256
+
+/* must match bytecode defines now; build autogenerate? */
+DUK_LOCAL const char *duk__bc_optab[64] = {
+       "LDREG",    "STREG",    "LDCONST",  "LDINT",    "LDINTX",   "MPUTOBJ",  "MPUTOBJI", "MPUTARR",  "MPUTARRI", "NEW",
+       "NEWI",     "REGEXP",   "CSREG",    "CSREGI",   "GETVAR",   "PUTVAR",   "DECLVAR",  "DELVAR",   "CSVAR",    "CSVARI",
+       "CLOSURE",  "GETPROP",  "PUTPROP",  "DELPROP",  "CSPROP",   "CSPROPI",  "ADD",      "SUB",      "MUL",      "DIV",
+       "MOD",      "BAND",     "BOR",      "BXOR",     "BASL",     "BLSR",     "BASR",     "EQ",       "NEQ",      "SEQ",
+       "SNEQ",     "GT",       "GE",       "LT",       "LE",       "IF",       "JUMP",     "RETURN",   "CALL",     "CALLI",
+       "TRYCATCH", "EXTRA",    "PREINCR",  "PREDECR",  "POSTINCR", "POSTDECR", "PREINCV",  "PREDECV",  "POSTINCV", "POSTDECV",
+       "PREINCP",  "PREDECP",  "POSTINCP", "POSTDECP"
+};
+
+DUK_LOCAL const char *duk__bc_extraoptab[256] = {
+       "NOP", "INVALID", "LDTHIS", "LDUNDEF", "LDNULL", "LDTRUE", "LDFALSE", "NEWOBJ", "NEWARR", "SETALEN",
+       "TYPEOF", "TYPEOFID", "INITENUM", "NEXTENUM", "INITSET", "INITSETI", "INITGET", "INITGETI", "ENDTRY", "ENDCATCH",
+       "ENDFIN", "THROW", "INVLHS", "UNM", "UNP", "DEBUGGER", "BREAK", "CONTINUE", "BNOT", "LNOT",
+       "INSTOF", "IN", "LABEL", "ENDLABEL", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
+
+       "XXX", "XXX", "XXX", "XXX", "XXX", "XXX"
+};
+
+typedef struct duk__dprint_state duk__dprint_state;
+struct duk__dprint_state {
+       duk_fixedbuffer *fb;
+
+       /* loop_stack_index could be perhaps be replaced by 'depth', but it's nice
+        * to not couple these two mechanisms unnecessarily.
+        */
+       duk_hobject *loop_stack[DUK__LOOP_STACK_DEPTH];
+       duk_int_t loop_stack_index;
+       duk_int_t loop_stack_limit;
+
+       duk_int_t depth;
+       duk_int_t depth_limit;
+
+       duk_bool_t pointer;
+       duk_bool_t heavy;
+       duk_bool_t binary;
+       duk_bool_t follow_proto;
+       duk_bool_t internal;
+       duk_bool_t hexdump;
+};
+
+/* helpers */
+DUK_LOCAL_DECL void duk__print_hstring(duk__dprint_state *st, duk_hstring *k, duk_bool_t quotes);
+DUK_LOCAL_DECL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h);
+DUK_LOCAL_DECL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h);
+DUK_LOCAL_DECL void duk__print_tval(duk__dprint_state *st, duk_tval *tv);
+DUK_LOCAL_DECL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins);
+DUK_LOCAL_DECL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
+DUK_LOCAL_DECL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h);
+DUK_LOCAL_DECL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h);
+
+DUK_LOCAL void duk__print_shared_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
+       duk_fixedbuffer *fb = st->fb;
+
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "(%p)", (void *) h);
+       }
+
+       if (!h) {
+               return;
+       }
+
+       if (st->binary) {
+               duk_size_t i;
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+               for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
+                       duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
+               }
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+       }
+
+#ifdef DUK_USE_REFERENCE_COUNTING  /* currently implicitly also DUK_USE_DOUBLE_LINKED_HEAP */
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "[h_next=%p,h_prev=%p,h_refcount=%lu,h_flags=%08lx,type=%ld,"
+                              "reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+                              (void *) DUK_HEAPHDR_GET_NEXT(NULL, h),
+                              (void *) DUK_HEAPHDR_GET_PREV(NULL, h),
+                              (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(h),
+                              (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
+                              (long) DUK_HEAPHDR_GET_TYPE(h),
+                              (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
+       }
+#else
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "[h_next=%p,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+                              (void *) DUK_HEAPHDR_GET_NEXT(NULL, h),
+                              (unsigned long) DUK_HEAPHDR_GET_FLAGS(h),
+                              (long) DUK_HEAPHDR_GET_TYPE(h),
+                              (long) (DUK_HEAPHDR_HAS_REACHABLE(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_TEMPROOT(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZABLE(h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZED(h) ? 1 : 0));
+       }
+#endif
+}
+
+DUK_LOCAL void duk__print_shared_heaphdr_string(duk__dprint_state *st, duk_heaphdr_string *h) {
+       duk_fixedbuffer *fb = st->fb;
+
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "(%p)", (void *) h);
+       }
+
+       if (!h) {
+               return;
+       }
+
+       if (st->binary) {
+               duk_size_t i;
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+               for (i = 0; i < (duk_size_t) sizeof(*h); i++) {
+                       duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)h)[i]);
+               }
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+       }
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "[h_refcount=%lu,h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+                              (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h),
+                              (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
+                              (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
+                              (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
+       }
+#else
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "[h_flags=%08lx,type=%ld,reachable=%ld,temproot=%ld,finalizable=%ld,finalized=%ld]",
+                              (unsigned long) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
+                              (long) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
+                              (long) (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h) ? 1 : 0),
+                              (long) (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h) ? 1 : 0));
+       }
+#endif
+}
+
+DUK_LOCAL void duk__print_hstring(duk__dprint_state *st, duk_hstring *h, duk_bool_t quotes) {
+       duk_fixedbuffer *fb = st->fb;
+       const duk_uint8_t *p;
+       const duk_uint8_t *p_end;
+
+       /* terminal type: no depth check */
+
+       if (duk_fb_is_full(fb)) {
+               return;
+       }
+
+       duk__print_shared_heaphdr_string(st, &h->hdr);
+
+       if (!h) {
+               duk_fb_put_cstring(fb, "NULL");
+               return;
+       }
+
+       p = DUK_HSTRING_GET_DATA(h);
+       p_end = p + DUK_HSTRING_GET_BYTELEN(h);
+
+       if (p_end > p && p[0] == DUK_ASC_UNDERSCORE) {
+               /* if property key begins with underscore, encode it with
+                * forced quotes (e.g. "_Foo") to distinguish it from encoded
+                * internal properties (e.g. \xffBar -> _Bar).
+                */
+               quotes = 1;
+       }
+
+       if (quotes) {
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
+       }
+       while (p < p_end) {
+               duk_uint8_t ch = *p++;
+
+               /* two special escapes: '\' and '"', other printables as is */
+               if (ch == '\\') {
+                       duk_fb_sprintf(fb, "\\\\");
+               } else if (ch == '"') {
+                       duk_fb_sprintf(fb, "\\\"");
+               } else if (ch >= 0x20 && ch <= 0x7e) {
+                       duk_fb_put_byte(fb, ch);
+               } else if (ch == 0xff && !quotes) {
+                       /* encode \xffBar as _Bar if no quotes are applied, this is for
+                        * readable internal keys.
+                        */
+                       duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_UNDERSCORE);
+               } else {
+                       duk_fb_sprintf(fb, "\\x%02lx", (unsigned long) ch);
+               }
+       }
+       if (quotes) {
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_DOUBLEQUOTE);
+       }
+#ifdef DUK_USE_REFERENCE_COUNTING
+       /* XXX: limit to quoted strings only, to save keys from being cluttered? */
+       duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
+#endif
+}
+
+#ifdef DUK__COMMA
+#undef DUK__COMMA
+#endif
+#define DUK__COMMA()  do { \
+               if (first) { \
+                       first = 0; \
+               } else { \
+                       duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA); \
+               } \
+       } while (0)
+
+DUK_LOCAL void duk__print_hobject(duk__dprint_state *st, duk_hobject *h) {
+       duk_fixedbuffer *fb = st->fb;
+       duk_uint_fast32_t i;
+       duk_tval *tv;
+       duk_hstring *key;
+       duk_bool_t first = 1;
+       const char *brace1 = "{";
+       const char *brace2 = "}";
+       duk_bool_t pushed_loopstack = 0;
+
+       if (duk_fb_is_full(fb)) {
+               return;
+       }
+
+       duk__print_shared_heaphdr(st, &h->hdr);
+
+       if (h && DUK_HOBJECT_HAS_ARRAY_PART(h)) {
+               brace1 = "[";
+               brace2 = "]";
+       }
+
+       if (!h) {
+               duk_fb_put_cstring(fb, "NULL");
+               goto finished;
+       }
+
+       if (st->depth >= st->depth_limit) {
+               if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+                       duk_fb_sprintf(fb, "%sobject/compiledfunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+               } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+                       duk_fb_sprintf(fb, "%sobject/nativefunction %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+               } else if (DUK_HOBJECT_IS_THREAD(h)) {
+                       duk_fb_sprintf(fb, "%sobject/thread %p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+               } else {
+                       duk_fb_sprintf(fb, "%sobject %p%s", (const char *) brace1, (void *) h, (const char *) brace2);  /* may be NULL */
+               }
+               return;
+       }
+
+       for (i = 0; i < (duk_uint_fast32_t) st->loop_stack_index; i++) {
+               if (st->loop_stack[i] == h) {
+                       duk_fb_sprintf(fb, "%sLOOP:%p%s", (const char *) brace1, (void *) h, (const char *) brace2);
+                       return;
+               }
+       }
+
+       /* after this, return paths should 'goto finished' for decrement */
+       st->depth++;
+
+       if (st->loop_stack_index >= st->loop_stack_limit) {
+               duk_fb_sprintf(fb, "%sOUT-OF-LOOP-STACK%s", (const char *) brace1, (const char *) brace2);
+               goto finished;
+       }
+       st->loop_stack[st->loop_stack_index++] = h;
+       pushed_loopstack = 1;
+
+       /*
+        *  Notation: double underscore used for internal properties which are not
+        *  stored in the property allocation (e.g. '__valstack').
+        */
+
+       duk_fb_put_cstring(fb, brace1);
+
+       if (DUK_HOBJECT_GET_PROPS(NULL, h)) {
+               duk_uint32_t a_limit;
+
+               a_limit = DUK_HOBJECT_GET_ASIZE(h);
+               if (st->internal) {
+                       /* dump all allocated entries, unused entries print as 'unused',
+                        * note that these may extend beyond current 'length' and look
+                        * a bit funny.
+                        */
+               } else {
+                       /* leave out trailing 'unused' elements */
+                       while (a_limit > 0) {
+                               tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, a_limit - 1);
+                               if (!DUK_TVAL_IS_UNUSED(tv)) {
+                                       break;
+                               }
+                               a_limit--;
+                       }
+               }
+
+               for (i = 0; i < a_limit; i++) {
+                       tv = DUK_HOBJECT_A_GET_VALUE_PTR(NULL, h, i);
+                       DUK__COMMA();
+                       duk__print_tval(st, tv);
+               }
+               for (i = 0; i < DUK_HOBJECT_GET_ENEXT(h); i++) {
+                       key = DUK_HOBJECT_E_GET_KEY(NULL, h, i);
+                       if (!key) {
+                               continue;
+                       }
+                       if (!st->internal &&
+                           DUK_HSTRING_GET_BYTELEN(key) > 0 &&
+                           DUK_HSTRING_GET_DATA(key)[0] == 0xff) {
+                               /* XXX: use DUK_HSTRING_FLAG_INTERNAL? */
+                               continue;
+                       }
+                       DUK__COMMA();
+                       duk__print_hstring(st, key, 0);
+                       duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COLON);
+                       if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(NULL, h, i)) {
+                               duk_fb_sprintf(fb, "[get:%p,set:%p]",
+                                              (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.get,
+                                              (void *) DUK_HOBJECT_E_GET_VALUE(NULL, h, i).a.set);
+                       } else {
+                               tv = &DUK_HOBJECT_E_GET_VALUE(NULL, h, i).v;
+                               duk__print_tval(st, tv);
+                       }
+                       if (st->heavy) {
+                               duk_fb_sprintf(fb, "<%02lx>", (unsigned long) DUK_HOBJECT_E_GET_FLAGS(NULL, h, i));
+                       }
+               }
+       }
+       if (st->internal) {
+               if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__extensible:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__constructable:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_BOUND(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__bound:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__compiledfunction:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__nativefunction:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_THREAD(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__thread:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_ARRAY_PART(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__array_part:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_STRICT(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__strict:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_NEWENV(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__newenv:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_NAMEBINDING(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__namebinding:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_CREATEARGS(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__createargs:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_ENVRECCLOSED(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__envrecclosed:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_array:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_stringobj:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_arguments:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_dukfunc:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_IS_BUFFEROBJECT(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_bufferobj:true");
+               } else {
+                       ;
+               }
+               if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h)) {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__exotic_proxyobj:true");
+               } else {
+                       ;
+               }
+       }
+       if (st->internal && DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
+               duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
+               DUK__COMMA(); duk_fb_put_cstring(fb, "__data:");
+               duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(NULL, f));
+               DUK__COMMA(); duk_fb_sprintf(fb, "__nregs:%ld", (long) f->nregs);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
+#if defined(DUK_USE_DEBUGGER_SUPPORT)
+               DUK__COMMA(); duk_fb_sprintf(fb, "__start_line:%ld", (long) f->start_line);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__end_line:%ld", (long) f->end_line);
+#endif
+               DUK__COMMA(); duk_fb_put_cstring(fb, "__data:");
+               duk__print_hbuffer(st, (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(NULL, f));
+       } else if (st->internal && DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
+               duk_hnativefunction *f = (duk_hnativefunction *) h;
+               DUK__COMMA(); duk_fb_sprintf(fb, "__func:");
+               duk_fb_put_funcptr(fb, (duk_uint8_t *) &f->func, sizeof(f->func));
+               DUK__COMMA(); duk_fb_sprintf(fb, "__nargs:%ld", (long) f->nargs);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__magic:%ld", (long) f->magic);
+       } else if (st->internal && DUK_HOBJECT_IS_BUFFEROBJECT(h)) {
+               duk_hbufferobject *b = (duk_hbufferobject *) h;
+               DUK__COMMA(); duk_fb_sprintf(fb, "__buf:");
+               duk__print_hbuffer(st, (duk_hbuffer *) b->buf);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__offset:%ld", (long) b->offset);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__length:%ld", (long) b->length);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__shift:%ld", (long) b->shift);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__elemtype:%ld", (long) b->elem_type);
+       } else if (st->internal && DUK_HOBJECT_IS_THREAD(h)) {
+               duk_hthread *t = (duk_hthread *) h;
+               DUK__COMMA(); duk_fb_sprintf(fb, "__strict:%ld", (long) t->strict);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__state:%ld", (long) t->state);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__unused1:%ld", (long) t->unused1);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__unused2:%ld", (long) t->unused2);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_max:%ld", (long) t->valstack_max);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__callstack_max:%ld", (long) t->callstack_max);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_max:%ld", (long) t->catchstack_max);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__valstack:%p", (void *) t->valstack);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_end:%p/%ld", (void *) t->valstack_end, (long) (t->valstack_end - t->valstack));
+               DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_bottom:%p/%ld", (void *) t->valstack_bottom, (long) (t->valstack_bottom - t->valstack));
+               DUK__COMMA(); duk_fb_sprintf(fb, "__valstack_top:%p/%ld", (void *) t->valstack_top, (long) (t->valstack_top - t->valstack));
+               DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack:%p", (void *) t->catchstack);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_size:%ld", (long) t->catchstack_size);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__catchstack_top:%ld", (long) t->catchstack_top);
+               DUK__COMMA(); duk_fb_sprintf(fb, "__resumer:"); duk__print_hobject(st, (duk_hobject *) t->resumer);
+               /* XXX: print built-ins array? */
+
+       }
+#ifdef DUK_USE_REFERENCE_COUNTING
+       if (st->internal) {
+               DUK__COMMA(); duk_fb_sprintf(fb, "__refcount:%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h));
+       }
+#endif
+       if (st->internal) {
+               DUK__COMMA(); duk_fb_sprintf(fb, "__class:%ld", (long) DUK_HOBJECT_GET_CLASS_NUMBER(h));
+       }
+
+       DUK__COMMA(); duk_fb_sprintf(fb, "__heapptr:%p", (void *) h);  /* own pointer */
+
+       /* prototype should be last, for readability */
+       if (DUK_HOBJECT_GET_PROTOTYPE(NULL, h)) {
+               if (st->follow_proto) {
+                       DUK__COMMA(); duk_fb_put_cstring(fb, "__prototype:"); duk__print_hobject(st, DUK_HOBJECT_GET_PROTOTYPE(NULL, h));
+               } else {
+                       DUK__COMMA(); duk_fb_sprintf(fb, "__prototype:%p", (void *) DUK_HOBJECT_GET_PROTOTYPE(NULL, h));
+               }
+       }
+
+       duk_fb_put_cstring(fb, brace2);
+
+#if defined(DUK_USE_HOBJECT_HASH_PART)
+       if (st->heavy && DUK_HOBJECT_GET_HSIZE(h) > 0) {
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
+               for (i = 0; i < DUK_HOBJECT_GET_HSIZE(h); i++) {
+                       duk_uint_t h_idx = DUK_HOBJECT_H_GET_INDEX(NULL, h, i);
+                       if (i > 0) {
+                               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_COMMA);
+                       }
+                       if (h_idx == DUK_HOBJECT_HASHIDX_UNUSED) {
+                               duk_fb_sprintf(fb, "u");
+                       } else if (h_idx == DUK_HOBJECT_HASHIDX_DELETED) {
+                               duk_fb_sprintf(fb, "d");
+                       } else {
+                               duk_fb_sprintf(fb, "%ld", (long) h_idx);
+                       }
+               }
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
+       }
+#endif
+
+ finished:
+       st->depth--;
+       if (pushed_loopstack) {
+               st->loop_stack_index--;
+               st->loop_stack[st->loop_stack_index] = NULL;
+       }
+}
+
+#undef DUK__COMMA
+
+DUK_LOCAL void duk__print_hbuffer(duk__dprint_state *st, duk_hbuffer *h) {
+       duk_fixedbuffer *fb = st->fb;
+       duk_size_t i, n;
+       duk_uint8_t *p;
+
+       if (duk_fb_is_full(fb)) {
+               return;
+       }
+
+       /* terminal type: no depth check */
+
+       if (!h) {
+               duk_fb_put_cstring(fb, "NULL");
+               return;
+       }
+
+       if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
+               if (DUK_HBUFFER_HAS_EXTERNAL(h)) {
+                       duk_hbuffer_external *g = (duk_hbuffer_external *) h;
+                       duk_fb_sprintf(fb, "buffer:external:%p:%ld",
+                                      (void *) DUK_HBUFFER_EXTERNAL_GET_DATA_PTR(NULL, g),
+                                      (long) DUK_HBUFFER_EXTERNAL_GET_SIZE(g));
+               } else {
+                       duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
+                       duk_fb_sprintf(fb, "buffer:dynamic:%p:%ld",
+                                      (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(NULL, g),
+                                      (long) DUK_HBUFFER_DYNAMIC_GET_SIZE(g));
+               }
+       } else {
+               duk_fb_sprintf(fb, "buffer:fixed:%ld", (long) DUK_HBUFFER_GET_SIZE(h));
+       }
+
+#ifdef DUK_USE_REFERENCE_COUNTING
+       duk_fb_sprintf(fb, "/%lu", (unsigned long) DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
+#endif
+
+       if (st->hexdump) {
+               duk_fb_sprintf(fb, "=[");
+               n = DUK_HBUFFER_GET_SIZE(h);
+               p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(NULL, h);
+               for (i = 0; i < n; i++) {
+                       duk_fb_sprintf(fb, "%02lx", (unsigned long) p[i]);
+               }
+               duk_fb_sprintf(fb, "]");
+       }
+}
+
+DUK_LOCAL void duk__print_heaphdr(duk__dprint_state *st, duk_heaphdr *h) {
+       duk_fixedbuffer *fb = st->fb;
+
+       if (duk_fb_is_full(fb)) {
+               return;
+       }
+
+       if (!h) {
+               duk_fb_put_cstring(fb, "NULL");
+               return;
+       }
+
+       switch (DUK_HEAPHDR_GET_TYPE(h)) {
+       case DUK_HTYPE_STRING:
+               duk__print_hstring(st, (duk_hstring *) h, 1);
+               break;
+       case DUK_HTYPE_OBJECT:
+               duk__print_hobject(st, (duk_hobject *) h);
+               break;
+       case DUK_HTYPE_BUFFER:
+               duk__print_hbuffer(st, (duk_hbuffer *) h);
+               break;
+       default:
+               duk_fb_sprintf(fb, "[unknown htype %ld]", (long) DUK_HEAPHDR_GET_TYPE(h));
+               break;
+       }
+}
+
+DUK_LOCAL void duk__print_tval(duk__dprint_state *st, duk_tval *tv) {
+       duk_fixedbuffer *fb = st->fb;
+
+       if (duk_fb_is_full(fb)) {
+               return;
+       }
+
+       /* depth check is done when printing an actual type */
+
+       if (st->heavy) {
+               duk_fb_sprintf(fb, "(%p)", (void *) tv);
+       }
+
+       if (!tv) {
+               duk_fb_put_cstring(fb, "NULL");
+               return;
+       }
+
+       if (st->binary) {
+               duk_size_t i;
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LBRACKET);
+               for (i = 0; i < (duk_size_t) sizeof(*tv); i++) {
+                       duk_fb_sprintf(fb, "%02lx", (unsigned long) ((duk_uint8_t *)tv)[i]);
+               }
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RBRACKET);
+       }
+
+       if (st->heavy) {
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_LANGLE);
+       }
+       switch (DUK_TVAL_GET_TAG(tv)) {
+       case DUK_TAG_UNDEFINED: {
+               duk_fb_put_cstring(fb, "undefined");
+               break;
+       }
+       case DUK_TAG_UNUSED: {
+               duk_fb_put_cstring(fb, "unused");
+               break;
+       }
+       case DUK_TAG_NULL: {
+               duk_fb_put_cstring(fb, "null");
+               break;
+       }
+       case DUK_TAG_BOOLEAN: {
+               duk_fb_put_cstring(fb, DUK_TVAL_GET_BOOLEAN(tv) ? "true" : "false");
+               break;
+       }
+       case DUK_TAG_STRING: {
+               /* Note: string is a terminal heap object, so no depth check here */
+               duk__print_hstring(st, DUK_TVAL_GET_STRING(tv), 1);
+               break;
+       }
+       case DUK_TAG_OBJECT: {
+               duk__print_hobject(st, DUK_TVAL_GET_OBJECT(tv));
+               break;
+       }
+       case DUK_TAG_BUFFER: {
+               duk__print_hbuffer(st, DUK_TVAL_GET_BUFFER(tv));
+               break;
+       }
+       case DUK_TAG_POINTER: {
+               duk_fb_sprintf(fb, "pointer:%p", (void *) DUK_TVAL_GET_POINTER(tv));
+               break;
+       }
+       case DUK_TAG_LIGHTFUNC: {
+               duk_c_function func;
+               duk_small_uint_t lf_flags;
+
+               DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags);
+               duk_fb_sprintf(fb, "lightfunc:");
+               duk_fb_put_funcptr(fb, (duk_uint8_t *) &func, sizeof(func));
+               duk_fb_sprintf(fb, ":%04lx", (long) lf_flags);
+               break;
+       }
+#if defined(DUK_USE_FASTINT)
+       case DUK_TAG_FASTINT:
+#endif
+       default: {
+               /* IEEE double is approximately 16 decimal digits; print a couple extra */
+               DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
+               DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
+               duk_fb_sprintf(fb, "%.18g", (double) DUK_TVAL_GET_NUMBER(tv));
+               break;
+       }
+       }
+       if (st->heavy) {
+               duk_fb_put_byte(fb, (duk_uint8_t) DUK_ASC_RANGLE);
+       }
+}
+
+DUK_LOCAL void duk__print_instr(duk__dprint_state *st, duk_instr_t ins) {
+       duk_fixedbuffer *fb = st->fb;
+       duk_small_int_t op;
+       const char *op_name;
+       const char *extraop_name;
+
+       op = (duk_small_int_t) DUK_DEC_OP(ins);
+       op_name = duk__bc_optab[op];
+
+       /* XXX: option to fix opcode length so it lines up nicely */
+
+       if (op == DUK_OP_EXTRA) {
+               extraop_name = duk__bc_extraoptab[DUK_DEC_A(ins)];
+
+               duk_fb_sprintf(fb, "%s %ld, %ld",
+                              (const char *) extraop_name, (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
+       } else if (op == DUK_OP_JUMP) {
+               duk_int_t diff1 = DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;  /* from next pc */
+               duk_int_t diff2 = diff1 + 1;                            /* from curr pc */
+
+               duk_fb_sprintf(fb, "%s %ld (to pc%c%ld)",
+                              (const char *) op_name, (long) diff1,
+                              (int) (diff2 >= 0 ? '+' : '-'),  /* char format: use int */
+                              (long) (diff2 >= 0 ? diff2 : -diff2));
+       } else {
+               duk_fb_sprintf(fb, "%s %ld, %ld, %ld",
+                              (const char *) op_name, (long) DUK_DEC_A(ins),
+                              (long) DUK_DEC_B(ins), (long) DUK_DEC_C(ins));
+       }
+}
+
+DUK_LOCAL void duk__print_opcode(duk__dprint_state *st, duk_small_int_t opcode) {
+       duk_fixedbuffer *fb = st->fb;
+
+       if (opcode < DUK_BC_OP_MIN || opcode > DUK_BC_OP_MAX) {
+               duk_fb_sprintf(fb, "?(%ld)", (long) opcode);
+       } else {
+               duk_fb_sprintf(fb, "%s", (const char *) duk__bc_optab[opcode]);
+       }
+}
+
+DUK_INTERNAL duk_int_t duk_debug_vsnprintf(char *str, duk_size_t size, const char *format, va_list ap) {
+       duk_fixedbuffer fb;
+       const char *p = format;
+       const char *p_end = p + DUK_STRLEN(format);
+       duk_int_t retval;
+
+       DUK_MEMZERO(&fb, sizeof(fb));
+       fb.buffer = (duk_uint8_t *) str;
+       fb.length = size;
+       fb.offset = 0;
+       fb.truncated = 0;
+
+       while (p < p_end) {
+               char ch = *p++;
+               const char *p_begfmt = NULL;
+               duk_bool_t got_exclamation = 0;
+               duk_bool_t got_long = 0;  /* %lf, %ld etc */
+               duk__dprint_state st;
+
+               if (ch != DUK_ASC_PERCENT) {
+                       duk_fb_put_byte(&fb, (duk_uint8_t) ch);
+                       continue;
+               }
+
+               /*
+                *  Format tag parsing.  Since we don't understand all the
+                *  possible format tags allowed, we just scan for a terminating
+                *  specifier and keep track of relevant modifiers that we do
+                *  understand.  See man 3 printf.
+                */
+
+               DUK_MEMZERO(&st, sizeof(st));
+               st.fb = &fb;
+               st.depth = 0;
+               st.depth_limit = 1;
+               st.loop_stack_index = 0;
+               st.loop_stack_limit = DUK__LOOP_STACK_DEPTH;
+
+               p_begfmt = p - 1;
+               while (p < p_end) {
+                       ch = *p++;
+
+                       if (ch == DUK_ASC_STAR) {
+                               /* unsupported: would consume multiple args */
+                               goto error;
+                       } else if (ch == DUK_ASC_PERCENT) {
+                               duk_fb_put_byte(&fb, (duk_uint8_t) DUK_ASC_PERCENT);
+                               break;
+                       } else if (ch == DUK_ASC_EXCLAMATION) {
+                               got_exclamation = 1;
+                       } else if (!got_exclamation && ch == DUK_ASC_LC_L) {
+                               got_long = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_LC_D) {
+                               st.depth_limit = DUK__DEEP_DEPTH_LIMIT;
+                       } else if (got_exclamation && ch == DUK_ASC_LC_P) {
+                               st.follow_proto = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_LC_I) {
+                               st.internal = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_LC_X) {
+                               st.hexdump = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_LC_H) {
+                               st.heavy = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_ATSIGN) {
+                               st.pointer = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_HASH) {
+                               st.binary = 1;
+                       } else if (got_exclamation && ch == DUK_ASC_UC_T) {
+                               duk_tval *t = va_arg(ap, duk_tval *);
+                               if (st.pointer && !st.heavy) {
+                                       duk_fb_sprintf(&fb, "(%p)", (void *) t);
+                               }
+                               duk__print_tval(&st, t);
+                               break;
+                       } else if (got_exclamation && ch == DUK_ASC_UC_O) {
+                               duk_heaphdr *t = va_arg(ap, duk_heaphdr *);
+                               if (st.pointer && !st.heavy) {
+                                       duk_fb_sprintf(&fb, "(%p)", (void *) t);
+                               }
+                               duk__print_heaphdr(&st, t);
+                               break;
+                       } else if (got_exclamation && ch == DUK_ASC_UC_I) {
+                               duk_instr_t t = va_arg(ap, duk_instr_t);
+                               duk__print_instr(&st, t);
+                               break;
+                       } else if (got_exclamation && ch == DUK_ASC_UC_C) {
+                               long t = va_arg(ap, long);
+                               duk__print_opcode(&st, (duk_small_int_t) t);
+                               break;
+                       } else if (!got_exclamation && strchr(DUK__ALLOWED_STANDARD_SPECIFIERS, (int) ch)) {
+                               char fmtbuf[DUK__MAX_FORMAT_TAG_LENGTH];
+                               duk_size_t fmtlen;
+
+                               DUK_ASSERT(p >= p_begfmt);
+                               fmtlen = (duk_size_t) (p - p_begfmt);
+                               if (fmtlen >= sizeof(fmtbuf)) {
+                                       /* format is too large, abort */
+                                       goto error;
+                               }
+                               DUK_MEMZERO(fmtbuf, sizeof(fmtbuf));
+                               DUK_MEMCPY(fmtbuf, p_begfmt, fmtlen);
+
+                               /* assume exactly 1 arg, which is why '*' is forbidden; arg size still
+                                * depends on type though.
+                                */
+
+                               if (ch == DUK_ASC_LC_F || ch == DUK_ASC_LC_G || ch == DUK_ASC_LC_E) {
+                                       /* %f and %lf both consume a 'long' */
+                                       double arg = va_arg(ap, double);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_D && got_long) {
+                                       /* %ld */
+                                       long arg = va_arg(ap, long);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_D) {
+                                       /* %d; only 16 bits are guaranteed */
+                                       int arg = va_arg(ap, int);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_U && got_long) {
+                                       /* %lu */
+                                       unsigned long arg = va_arg(ap, unsigned long);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_U) {
+                                       /* %u; only 16 bits are guaranteed */
+                                       unsigned int arg = va_arg(ap, unsigned int);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_X && got_long) {
+                                       /* %lx */
+                                       unsigned long arg = va_arg(ap, unsigned long);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_X) {
+                                       /* %x; only 16 bits are guaranteed */
+                                       unsigned int arg = va_arg(ap, unsigned int);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else if (ch == DUK_ASC_LC_S) {
+                                       /* %s */
+                                       const char *arg = va_arg(ap, const char *);
+                                       if (arg == NULL) {
+                                               /* '%s' and NULL is not portable, so special case
+                                                * it for debug printing.
+                                                */
+                                               duk_fb_sprintf(&fb, "NULL");
+                                       } else {
+                                               duk_fb_sprintf(&fb, fmtbuf, arg);
+                                       }
+                               } else if (ch == DUK_ASC_LC_P) {
+                                       /* %p */
+                                       void *arg = va_arg(ap, void *);
+                                       if (arg == NULL) {
+                                               /* '%p' and NULL is portable, but special case it
+                                                * anyway to get a standard NULL marker in logs.
+                                                */
+                                               duk_fb_sprintf(&fb, "NULL");
+                                       } else {
+                                               duk_fb_sprintf(&fb, fmtbuf, arg);
+                                       }
+                               } else if (ch == DUK_ASC_LC_C) {
+                                       /* '%c', passed concretely as int */
+                                       int arg = va_arg(ap, int);
+                                       duk_fb_sprintf(&fb, fmtbuf, arg);
+                               } else {
+                                       /* Should not happen. */
+                                       duk_fb_sprintf(&fb, "INVALID-FORMAT(%s)", (const char *) fmtbuf);
+                               }
+                               break;
+                       } else {
+                               /* ignore */
+                       }
+               }
+       }
+       goto done;
+
+ error:
+       duk_fb_put_cstring(&fb, "FMTERR");
+       /* fall through */
+
+ done:
+       retval = (duk_int_t) fb.offset;
+       duk_fb_put_byte(&fb, (duk_uint8_t) 0);
+
+       /* return total chars written excluding terminator */
+       return retval;
+}
+
+#if 0  /*unused*/
+DUK_INTERNAL duk_int_t duk_debug_snprintf(char *str, duk_size_t size, const char *format, ...) {
+       duk_int_t retval;
+       va_list ap;
+       va_start(ap, format);
+       retval = duk_debug_vsnprintf(str, size, format, ap);
+       va_end(ap);
+       return retval;
+}
+#endif
+
+/* Formatting function pointers is tricky: there is no standard pointer for
+ * function pointers and the size of a function pointer may depend on the
+ * specific pointer type.  This helper formats a function pointer based on
+ * its memory layout to get something useful on most platforms.
+ */
+DUK_INTERNAL void duk_debug_format_funcptr(char *buf, duk_size_t buf_size, duk_uint8_t *fptr, duk_size_t fptr_size) {
+       duk_size_t i;
+       duk_uint8_t *p = (duk_uint8_t *) buf;
+       duk_uint8_t *p_end = (duk_uint8_t *) (buf + buf_size - 1);
+
+       DUK_MEMZERO(buf, buf_size);
+
+       for (i = 0; i < fptr_size; i++) {
+               duk_int_t left = (duk_int_t) (p_end - p);
+               duk_uint8_t ch;
+               if (left <= 0) {
+                       break;
+               }
+
+               /* Quite approximate but should be useful for little and big endian. */
+#ifdef DUK_USE_INTEGER_BE
+               ch = fptr[i];
+#else
+               ch = fptr[fptr_size - 1 - i];
+#endif
+               p += DUK_SNPRINTF((char *) p, left, "%02lx", (unsigned long) ch);
+       }
+}
+
+#endif  /* DUK_USE_DEBUG */