31601 : %!T",
31602 (duk_tval *) duk_get_tval(ctx, idx_reviver)));
31603 }
31604
31605 /* Final result is at stack top. */
31606
31607 DUK_DDD(DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08lx, result=%!T, stack_top=%ld",
31608 (duk_tval *) duk_get_tval(ctx, idx_value),
31609 (duk_tval *) duk_get_tval(ctx, idx_reviver),
31610 (unsigned long) flags,
31611 (duk_tval *) duk_get_tval(ctx, -1),
31612 (long) duk_get_top(ctx)));
31613
31614 DUK_ASSERT(duk_get_top(ctx) == entry_top + 1);
31615}
31616
31617DUK_INTERNAL
31618void duk_bi_json_stringify_helper(duk_context *ctx,
31619 duk_idx_t idx_value,
31620 duk_idx_t idx_replacer,
31621 duk_idx_t idx_space,
31622 duk_small_uint_t flags) {
31623 duk_hthread *thr = (duk_hthread *) ctx;
31624 duk_json_enc_ctx js_ctx_alloc;
31625 duk_json_enc_ctx *js_ctx = &js_ctx_alloc;
31626 duk_hobject *h;
31627 duk_idx_t idx_holder;
31628 duk_idx_t entry_top;
31629
31630 /* negative top-relative indices not allowed now */
31631 DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0);
31632 DUK_ASSERT(idx_replacer == DUK_INVALID_INDEX || idx_replacer >= 0);
31633 DUK_ASSERT(idx_space == DUK_INVALID_INDEX || idx_space >= 0);
31634
31635 DUK_DDD(DUK_DDDPRINT("JSON stringify start:
value=%!T, replacer=%!T, space=%!T, flags=0x%08lx, stack_top=%ld
",
31636 (duk_tval *) duk_get_tval(ctx, idx_value),
31637 (duk_tval *) duk_get_tval(ctx, idx_replacer),
31638 (duk_tval *) duk_get_tval(ctx, idx_space),
31639 (unsigned long) flags,
31640 (long) duk_get_top(ctx)));
31641
31642 entry_top = duk_get_top(ctx);
31643
31644 /*
31645 * Context init
31646 */
31647
31648 DUK_MEMZERO(&js_ctx_alloc, sizeof(js_ctx_alloc));
31649 js_ctx->thr = thr;
31650#ifdef DUK_USE_EXPLICIT_NULL_INIT
31651 js_ctx->h_replacer = NULL;
31652 js_ctx->h_gap = NULL;
31653#endif
31654 js_ctx->idx_proplist = -1;
31655
31656 /* Flag handling currently assumes that flags are consistent. This is OK
31657 * because the call sites are now strictly controlled.
31658 */
31659
31660 js_ctx->flags = flags;
31661 js_ctx->flag_ascii_only = flags & DUK_JSON_FLAG_ASCII_ONLY;
31662 js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES;
31663#ifdef DUK_USE_JX
31664 js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM;
31665#endif
31666#ifdef DUK_USE_JC
31667 js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE;
31668#endif
31669#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
31670 js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE);
31671#endif
31672
31673 /* The #ifdef clutter here handles the JX/JC enable/disable
31674 * combinations properly.
31675 */
31676#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
31677 js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_NULL; /* standard JSON; array gaps */
31678#if defined(DUK_USE_JX)
31679 if (flags & DUK_JSON_FLAG_EXT_CUSTOM) {
31680 js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED;
31681 js_ctx->stridx_custom_nan = DUK_STRIDX_NAN;
31682 js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY;
31683 js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY;
31684 js_ctx->stridx_custom_function =
31685 (flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES) ?
31686 DUK_STRIDX_JSON_EXT_FUNCTION2 :
31687 DUK_STRIDX_JSON_EXT_FUNCTION1;
31688 }
31689#endif /* DUK_USE_JX */
31690#if defined(DUK_USE_JX) && defined(DUK_USE_JC)
31691 else
31692#endif /* DUK_USE_JX && DUK_USE_JC */
31693#if defined(DUK_USE_JC)
31694 if (js_ctx->flags & DUK_JSON_FLAG_EXT_COMPATIBLE) {
31695 js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED;
31696 js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN;
31697 js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF;
31698 js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF;
31699 js_ctx->stridx_custom_function = DUK_STRIDX_JSON_EXT_FUNCTION1;
31700 }
31701#endif /* DUK_USE_JC */
31702#endif /* DUK_USE_JX || DUK_USE_JC */
31703
31704#if defined(DUK_USE_JX) || defined(DUK_USE_JC)
31705 if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
31706 DUK_JSON_FLAG_EXT_COMPATIBLE)) {
31707 DUK_ASSERT(js_ctx->mask_for_undefined == 0); /* already zero */
31708 }
31709 else
31710#endif /* DUK_USE_JX || DUK_USE_JC */
31711 {
31712 js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED |
31713 DUK_TYPE_MASK_POINTER |
31714 DUK_TYPE_MASK_BUFFER |
31715 DUK_TYPE_MASK_LIGHTFUNC;
31716 }
31717
31718 DUK_BW_INIT_PUSHBUF(thr, &js_ctx->bw, DUK__JSON_STRINGIFY_BUFSIZE);
31719
31720 js_ctx->idx_loop = duk_push_object_internal(ctx);
31721 DUK_ASSERT(js_ctx->idx_loop >= 0);
31722
31723 /* [ ... buf loop ] */
31724
31725 /*
31726 * Process replacer/proplist (2nd argument to JSON.stringify)
31727 */
31728
31729 h = duk_get_hobject(ctx, idx_replacer);
31730 if (h != NULL) {
31731 if (DUK_HOBJECT_IS_CALLABLE(h)) {
31732 js_ctx->h_replacer = h;
31733 } else if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
31734 /* Here the specification requires correct array index enumeration
31735 * which is a bit tricky for sparse arrays (it is handled by the
31736 * enum setup code). We now enumerate ancestors too, although the
31737 * specification is not very clear on whether that is required.
31738 */
31739
31740 duk_uarridx_t plist_idx = 0;
31741 duk_small_uint_t enum_flags;
31742
31743 js_ctx->idx_proplist = duk_push_array(ctx); /* XXX: array internal? */
31744
31745 enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY |
31746 DUK_ENUM_SORT_ARRAY_INDICES; /* expensive flag */
31747 duk_enum(ctx, idx_replacer, enum_flags);
31748 while (duk_next(ctx, -1 /*enum_index*/, 1 /*get_value*/)) {
31749 /* [ ... proplist enum_obj key val ] */
31750 if (duk__enc_allow_into_proplist(duk_get_tval(ctx, -1))) {
31751 /* XXX: duplicates should be eliminated here */
31752 DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept",
31753 (duk_tval *) duk_get_tval(ctx, -2),
31754 (duk_tval *) duk_get_tval(ctx, -1)));
31755 duk_to_string(ctx, -1); /* extra coercion of strings is OK */
31756 duk_put_prop_index(ctx, -4, plist_idx); /* -> [ ... proplist enum_obj key ] */
31757 plist_idx++;
31758 duk_pop(ctx);
31759 } else {
31760 DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject",
31761 (duk_tval *) duk_get_tval(ctx, -2),
31762 (duk_tval *) duk_get_tval(ctx, -1)));
31763 duk_pop_2(ctx);
31764 }
31765 }
31766 duk_pop(ctx); /* pop enum */
31767
31768 /* [ ... proplist ] */
31769 }
31770 }
31771
31772 /* [ ... buf loop (proplist) ] */
31773
31774 /*
31775 * Process space (3rd argument to JSON.stringify)
31776 */
31777
31778 h = duk_get_hobject(ctx, idx_space);
31779 if (h != NULL) {
31780 int c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
31781 if (c == DUK_HOBJECT_CLASS_NUMBER) {
31782 duk_to_number(ctx, idx_space);
31783 } else if (c == DUK_HOBJECT_CLASS_STRING) {
31784 duk_to_string(ctx, idx_space);
31785 }
31786 }
31787
31788 if (duk_is_number(ctx, idx_space)) {
31789 duk_small_int_t nspace;
31790 /* spaces[] must be static to allow initializer with old compilers like BCC */
31791 static const char spaces[10] = {
31792 DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
31793 DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
31794 DUK_ASC_SPACE, DUK_ASC_SPACE
31795 }; /* XXX: helper */
31796
31797 /* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */
31798 nspace = (duk_small_int_t) duk_to_int_clamped(ctx, idx_space, 0 /*minval*/, 10 /*maxval*/);
31799 DUK_ASSERT(nspace >= 0 && nspace <= 10);
31800
31801 duk_push_lstring(ctx, spaces, (duk_size_t) nspace);
31802 js_ctx->h_gap = duk_get_hstring(ctx, -1);
31803 DUK_ASSERT(js_ctx->h_gap != NULL);
31804 } else if (duk_is_string(ctx, idx_space)) {
31805 /* XXX: substring in-place at idx_place? */
31806 duk_dup(ctx, idx_space);
31807 duk_substring(ctx, -1, 0, 10); /* clamp to 10 chars */
31808 js_ctx->h_gap = duk_get_hstring(ctx, -1);
31809 DUK_ASSERT(js_ctx->h_gap != NULL);
31810 } else {
31811 /* nop */
31812 }
31813
31814 if (js_ctx->h_gap != NULL) {
31815 /* if gap is empty, behave as if not given at all */
31816 if (DUK_HSTRING_GET_CHARLEN(js_ctx->h_gap) == 0) {
31817 js_ctx->h_gap = NULL;
31818 }
31819 }
31820
31821 /* [ ... buf loop (proplist) (gap) ] */
31822
31823 /*
31824 * Fast path: assume no mutation, iterate object property tables
31825 * directly; bail out if that assumption doesn't hold.
31826 */
31827
31828#if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
31829 if (js_ctx->h_replacer == NULL && /* replacer is a mutation risk */
31830 js_ctx->idx_proplist == -1) { /* proplist is very rare */
31831 duk_int_t pcall_rc;
31832#ifdef DUK_USE_MARK_AND_SWEEP
31833 duk_small_uint_t prev_mark_and_sweep_base_flags;
31834#endif
31835
31836 DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path"));
31837
31838 /* Use recursion_limit to ensure we don't overwrite js_ctx->visiting[]
31839 * array so we don't need two counter checks in the fast path. The
31840 * slow path has a much larger recursion limit which we'll use if
31841 * necessary.
31842 */
31843 DUK_ASSERT(DUK_USE_JSON_ENC_RECLIMIT >= DUK_JSON_ENC_LOOPARRAY);
31844 js_ctx->recursion_limit = DUK_JSON_ENC_LOOPARRAY;
31845 DUK_ASSERT(js_ctx->recursion_depth == 0);
31846
31847 /* Execute the fast path in a protected call. If any error is thrown,
31848 * fall back to the slow path. This includes e.g. recursion limit
31849 * because the fast path has a smaller recursion limit (and simpler,
31850 * limited loop detection).
31851 */
31852
31853 duk_push_pointer(ctx, (void *) js_ctx);
31854 duk_dup(ctx, idx_value);
31855
31856#if defined(DUK_USE_MARK_AND_SWEEP)
31857 /* Must prevent finalizers which may have arbitrary side effects. */
31858 prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags;
31859 thr->heap->mark_and_sweep_base_flags |=
31860 DUK_MS_FLAG_NO_FINALIZERS | /* avoid attempts to add/remove object keys */
31861 DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* avoid attempt to compact any objects */
31862#endif
31863
31864 pcall_rc = duk_safe_call(ctx, duk__json_stringify_fast, 2 /*nargs*/, 0 /*nret*/);
31865
31866#if defined(DUK_USE_MARK_AND_SWEEP)
31867 thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
31868#endif
31869 if (pcall_rc == DUK_EXEC_SUCCESS) {
31870 DUK_DD(DUK_DDPRINT("fast path successful"));
31871 DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw);
31872 goto replace_finished;
31873 }
31874
31875 /* We come here for actual aborts (like encountering .toJSON())
31876 * but also for recursion/loop errors. Bufwriter size can be
31877 * kept because we'll probably need at least as much as we've
31878 * allocated so far.
31879 */
31880 DUK_D(DUK_DPRINT("fast path failed, serialize using slow path instead"));
31881 DUK_BW_RESET_SIZE(thr, &js_ctx->bw);
31882 js_ctx->recursion_depth = 0;
31883 }
31884#endif
31885
31886 /*
31887 * Create wrapper object and serialize
31888 */
31889
31890 idx_holder = duk_push_object(ctx);
31891 duk_dup(ctx, idx_value);
31892 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_EMPTY_STRING);
31893
31894 DUK_DDD(DUK_DDDPRINT("before: flags=0x%08lx, loop=%!T, replacer=%!O, "
31895 "proplist=%!T, gap=%!O, holder=%!T",
31896 (unsigned long) js_ctx->flags,
31897 (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop),
31898 (duk_heaphdr *) js_ctx->h_replacer,
31899 (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL),
31900 (duk_heaphdr *) js_ctx->h_gap,
31901 (duk_tval *) duk_get_tval(ctx, -1)));
31902
31903 /* serialize the wrapper with empty string key */
31904
31905 duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
31906
31907 /* [ ... buf loop (proplist) (gap) holder "" ] */
31908
31909 js_ctx->recursion_limit = DUK_USE_JSON_ENC_RECLIMIT;
31910 DUK_ASSERT(js_ctx->recursion_depth == 0);
31911
31912 if (DUK_UNLIKELY(duk__enc_value(js_ctx, idx_holder) == 0)) { /* [ ... holder key ] -> [ ... holder ] */
31913 /* Result is undefined. */
31914 duk_push_undefined(ctx);
31915 } else {
31916 /* Convert buffer to result string. */
31917 DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw);
31918 }
31919
31920 DUK_DDD(DUK_DDDPRINT("after: flags=0x%08lx, loop=%!T, replacer=%!O, "
31921 "proplist=%!T, gap=%!O, holder=%!T",
31922 (unsigned long) js_ctx->flags,
31923 (duk_tval *) duk_get_tval(ctx, js_ctx->idx_loop),
31924 (duk_heaphdr *) js_ctx->h_replacer,
31925 (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL),
31926 (duk_heaphdr *) js_ctx->h_gap,
31927 (duk_tval *) duk_get_tval(ctx, idx_holder)));
31928
31929 /* The stack has a variable shape here, so force it to the
31930 * desired one explicitly.
31931 */
31932