xref: /reactos/dll/win32/jscript/json.c (revision 36873c49)
1 /*
2  * Copyright 2016 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <math.h>
20 #include <assert.h>
21 
22 #include "jscript.h"
23 #include "parser.h"
24 
25 #include "wine/debug.h"
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
28 
29 static const WCHAR parseW[] = {'p','a','r','s','e',0};
30 static const WCHAR stringifyW[] = {'s','t','r','i','n','g','i','f','y',0};
31 
32 static const WCHAR nullW[] = {'n','u','l','l',0};
33 static const WCHAR trueW[] = {'t','r','u','e',0};
34 static const WCHAR falseW[] = {'f','a','l','s','e',0};
35 
36 static const WCHAR toJSONW[] = {'t','o','J','S','O','N',0};
37 
38 typedef struct {
39     const WCHAR *ptr;
40     const WCHAR *end;
41     script_ctx_t *ctx;
42 } json_parse_ctx_t;
43 
44 static BOOL is_json_space(WCHAR c)
45 {
46     return c == ' ' || c == '\t' || c == '\n' || c == '\r';
47 }
48 
49 static WCHAR skip_spaces(json_parse_ctx_t *ctx)
50 {
51     while(is_json_space(*ctx->ptr))
52         ctx->ptr++;
53     return *ctx->ptr;
54 }
55 
56 static BOOL is_keyword(json_parse_ctx_t *ctx, const WCHAR *keyword)
57 {
58     unsigned i;
59     for(i=0; keyword[i]; i++) {
60         if(!ctx->ptr[i] || keyword[i] != ctx->ptr[i])
61             return FALSE;
62     }
63     if(is_identifier_char(ctx->ptr[i]))
64         return FALSE;
65     ctx->ptr += i;
66     return TRUE;
67 }
68 
69 /* ECMA-262 5.1 Edition    15.12.1.1 */
70 static HRESULT parse_json_string(json_parse_ctx_t *ctx, WCHAR **r)
71 {
72     const WCHAR *ptr = ++ctx->ptr;
73     size_t len;
74     WCHAR *buf;
75 
76     while(*ctx->ptr && *ctx->ptr != '"') {
77         if(*ctx->ptr++ == '\\')
78             ctx->ptr++;
79     }
80     if(!*ctx->ptr) {
81         FIXME("unterminated string\n");
82         return E_FAIL;
83     }
84 
85     len = ctx->ptr-ptr;
86     buf = heap_alloc((len+1)*sizeof(WCHAR));
87     if(!buf)
88         return E_OUTOFMEMORY;
89     if(len)
90         memcpy(buf, ptr, len*sizeof(WCHAR));
91 
92     if(!unescape(buf, &len)) {
93         FIXME("unescape failed\n");
94         heap_free(buf);
95         return E_FAIL;
96     }
97 
98     buf[len] = 0;
99     ctx->ptr++;
100     *r = buf;
101     return S_OK;
102 }
103 
104 /* ECMA-262 5.1 Edition    15.12.1.2 */
105 static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r)
106 {
107     HRESULT hres;
108 
109     switch(skip_spaces(ctx)) {
110 
111     /* JSONNullLiteral */
112     case 'n':
113         if(!is_keyword(ctx, nullW))
114             break;
115         *r = jsval_null();
116         return S_OK;
117 
118     /* JSONBooleanLiteral */
119     case 't':
120         if(!is_keyword(ctx, trueW))
121             break;
122         *r = jsval_bool(TRUE);
123         return S_OK;
124     case 'f':
125         if(!is_keyword(ctx, falseW))
126             break;
127         *r = jsval_bool(FALSE);
128         return S_OK;
129 
130     /* JSONObject */
131     case '{': {
132         WCHAR *prop_name;
133         jsdisp_t *obj;
134         jsval_t val;
135 
136         hres = create_object(ctx->ctx, NULL, &obj);
137         if(FAILED(hres))
138             return hres;
139 
140         ctx->ptr++;
141         if(skip_spaces(ctx) == '}') {
142             ctx->ptr++;
143             *r = jsval_obj(obj);
144             return S_OK;
145         }
146 
147         while(1) {
148             if(*ctx->ptr != '"')
149                 break;
150             hres = parse_json_string(ctx, &prop_name);
151             if(FAILED(hres))
152                 break;
153 
154             if(skip_spaces(ctx) != ':') {
155                 FIXME("missing ':'\n");
156                 heap_free(prop_name);
157                 break;
158             }
159 
160             ctx->ptr++;
161             hres = parse_json_value(ctx, &val);
162             if(SUCCEEDED(hres)) {
163                 hres = jsdisp_propput_name(obj, prop_name, val);
164                 jsval_release(val);
165             }
166             heap_free(prop_name);
167             if(FAILED(hres))
168                 break;
169 
170             if(skip_spaces(ctx) == '}') {
171                 ctx->ptr++;
172                 *r = jsval_obj(obj);
173                 return S_OK;
174             }
175 
176             if(*ctx->ptr++ != ',') {
177                 FIXME("expected ','\n");
178                 break;
179             }
180             skip_spaces(ctx);
181         }
182 
183         jsdisp_release(obj);
184         break;
185     }
186 
187     /* JSONString */
188     case '"': {
189         WCHAR *string;
190         jsstr_t *str;
191 
192         hres = parse_json_string(ctx, &string);
193         if(FAILED(hres))
194             return hres;
195 
196         /* FIXME: avoid reallocation */
197         str = jsstr_alloc(string);
198         heap_free(string);
199         if(!str)
200             return E_OUTOFMEMORY;
201 
202         *r = jsval_string(str);
203         return S_OK;
204     }
205 
206     /* JSONArray */
207     case '[': {
208         jsdisp_t *array;
209         unsigned i = 0;
210         jsval_t val;
211 
212         hres = create_array(ctx->ctx, 0, &array);
213         if(FAILED(hres))
214             return hres;
215 
216         ctx->ptr++;
217         if(skip_spaces(ctx) == ']') {
218             ctx->ptr++;
219             *r = jsval_obj(array);
220             return S_OK;
221         }
222 
223         while(1) {
224             hres = parse_json_value(ctx, &val);
225             if(FAILED(hres))
226                 break;
227 
228             hres = jsdisp_propput_idx(array, i, val);
229             jsval_release(val);
230             if(FAILED(hres))
231                 break;
232 
233             if(skip_spaces(ctx) == ']') {
234                 ctx->ptr++;
235                 *r = jsval_obj(array);
236                 return S_OK;
237             }
238 
239             if(*ctx->ptr != ',') {
240                 FIXME("expected ','\n");
241                 break;
242             }
243 
244             ctx->ptr++;
245             i++;
246         }
247 
248         jsdisp_release(array);
249         break;
250     }
251 
252     /* JSONNumber */
253     default: {
254         int sign = 1;
255         double n;
256 
257         if(*ctx->ptr == '-') {
258             sign = -1;
259             ctx->ptr++;
260             skip_spaces(ctx);
261         }
262 
263         if(*ctx->ptr == '0' && ctx->ptr + 1 < ctx->end && iswdigit(ctx->ptr[1]))
264             break;
265 
266         hres = parse_decimal(&ctx->ptr, ctx->end, &n);
267         if(FAILED(hres))
268             break;
269 
270         *r = jsval_number(sign*n);
271         return S_OK;
272     }
273     }
274 
275     FIXME("Syntax error at %s\n", debugstr_w(ctx->ptr));
276     return E_FAIL;
277 }
278 
279 /* ECMA-262 5.1 Edition    15.12.2 */
280 static HRESULT JSON_parse(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
281 {
282     json_parse_ctx_t parse_ctx;
283     const WCHAR *buf;
284     jsstr_t *str;
285     jsval_t ret;
286     HRESULT hres;
287 
288     if(argc != 1) {
289         FIXME("Unsupported args\n");
290         return E_INVALIDARG;
291     }
292 
293     hres = to_flat_string(ctx, argv[0], &str, &buf);
294     if(FAILED(hres))
295         return hres;
296 
297     TRACE("%s\n", debugstr_w(buf));
298 
299     parse_ctx.ptr = buf;
300     parse_ctx.end = buf + jsstr_length(str);
301     parse_ctx.ctx = ctx;
302     hres = parse_json_value(&parse_ctx, &ret);
303     jsstr_release(str);
304     if(FAILED(hres))
305         return hres;
306 
307     if(skip_spaces(&parse_ctx)) {
308         FIXME("syntax error\n");
309         jsval_release(ret);
310         return E_FAIL;
311     }
312 
313     if(r)
314         *r = ret;
315     else
316         jsval_release(ret);
317     return S_OK;
318 }
319 
320 typedef struct {
321     script_ctx_t *ctx;
322 
323     WCHAR *buf;
324     size_t buf_size;
325     size_t buf_len;
326 
327     jsdisp_t **stack;
328     size_t stack_top;
329     size_t stack_size;
330 
331     WCHAR gap[11]; /* according to the spec, it's no longer than 10 chars */
332 } stringify_ctx_t;
333 
334 static BOOL stringify_push_obj(stringify_ctx_t *ctx, jsdisp_t *obj)
335 {
336     if(!ctx->stack_size) {
337         ctx->stack = heap_alloc(4*sizeof(*ctx->stack));
338         if(!ctx->stack)
339             return FALSE;
340         ctx->stack_size = 4;
341     }else if(ctx->stack_top == ctx->stack_size) {
342         jsdisp_t **new_stack;
343 
344         new_stack = heap_realloc(ctx->stack, ctx->stack_size*2*sizeof(*ctx->stack));
345         if(!new_stack)
346             return FALSE;
347         ctx->stack = new_stack;
348         ctx->stack_size *= 2;
349     }
350 
351     ctx->stack[ctx->stack_top++] = obj;
352     return TRUE;
353 }
354 
355 static void stringify_pop_obj(stringify_ctx_t *ctx)
356 {
357     ctx->stack_top--;
358 }
359 
360 static BOOL is_on_stack(stringify_ctx_t *ctx, jsdisp_t *obj)
361 {
362     size_t i = ctx->stack_top;
363     while(i--) {
364         if(ctx->stack[i] == obj)
365             return TRUE;
366     }
367     return FALSE;
368 }
369 
370 static BOOL append_string_len(stringify_ctx_t *ctx, const WCHAR *str, size_t len)
371 {
372     if(!ctx->buf_size) {
373         ctx->buf = heap_alloc(len*2*sizeof(WCHAR));
374         if(!ctx->buf)
375             return FALSE;
376         ctx->buf_size = len*2;
377     }else if(ctx->buf_len + len > ctx->buf_size) {
378         WCHAR *new_buf;
379         size_t new_size;
380 
381         new_size = ctx->buf_size * 2 + len;
382         new_buf = heap_realloc(ctx->buf, new_size*sizeof(WCHAR));
383         if(!new_buf)
384             return FALSE;
385         ctx->buf = new_buf;
386         ctx->buf_size = new_size;
387     }
388 
389     if(len)
390         memcpy(ctx->buf + ctx->buf_len, str, len*sizeof(WCHAR));
391     ctx->buf_len += len;
392     return TRUE;
393 }
394 
395 static inline BOOL append_string(stringify_ctx_t *ctx, const WCHAR *str)
396 {
397     return append_string_len(ctx, str, lstrlenW(str));
398 }
399 
400 static inline BOOL append_char(stringify_ctx_t *ctx, WCHAR c)
401 {
402     return append_string_len(ctx, &c, 1);
403 }
404 
405 static inline BOOL append_simple_quote(stringify_ctx_t *ctx, WCHAR c)
406 {
407     WCHAR str[] = {'\\',c};
408     return append_string_len(ctx, str, 2);
409 }
410 
411 static HRESULT maybe_to_primitive(script_ctx_t *ctx, jsval_t val, jsval_t *r)
412 {
413     jsdisp_t *obj;
414     HRESULT hres;
415 
416     if(!is_object_instance(val) || !get_object(val) || !(obj = iface_to_jsdisp(get_object(val))))
417         return jsval_copy(val, r);
418 
419     if(is_class(obj, JSCLASS_NUMBER)) {
420         double n;
421         hres = to_number(ctx, val, &n);
422         jsdisp_release(obj);
423         if(SUCCEEDED(hres))
424             *r = jsval_number(n);
425         return hres;
426     }
427 
428     if(is_class(obj, JSCLASS_STRING)) {
429         jsstr_t *str;
430         hres = to_string(ctx, val, &str);
431         jsdisp_release(obj);
432         if(SUCCEEDED(hres))
433             *r = jsval_string(str);
434         return hres;
435     }
436 
437     if(is_class(obj, JSCLASS_BOOLEAN)) {
438         *r = jsval_bool(bool_obj_value(obj));
439         jsdisp_release(obj);
440         return S_OK;
441     }
442 
443     *r = jsval_obj(obj);
444     return S_OK;
445 }
446 
447 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation Quote) */
448 static HRESULT json_quote(stringify_ctx_t *ctx, const WCHAR *ptr, size_t len)
449 {
450     if(!ptr || !append_char(ctx, '"'))
451         return E_OUTOFMEMORY;
452 
453     while(len--) {
454         switch(*ptr) {
455         case '"':
456         case '\\':
457             if(!append_simple_quote(ctx, *ptr))
458                 return E_OUTOFMEMORY;
459             break;
460         case '\b':
461             if(!append_simple_quote(ctx, 'b'))
462                 return E_OUTOFMEMORY;
463             break;
464         case '\f':
465             if(!append_simple_quote(ctx, 'f'))
466                 return E_OUTOFMEMORY;
467             break;
468         case '\n':
469             if(!append_simple_quote(ctx, 'n'))
470                 return E_OUTOFMEMORY;
471             break;
472         case '\r':
473             if(!append_simple_quote(ctx, 'r'))
474                 return E_OUTOFMEMORY;
475             break;
476         case '\t':
477             if(!append_simple_quote(ctx, 't'))
478                 return E_OUTOFMEMORY;
479             break;
480         default:
481             if(*ptr < ' ') {
482                 static const WCHAR formatW[] = {'\\','u','%','0','4','x',0};
483                 WCHAR buf[7];
484                 swprintf(buf, formatW, *ptr);
485                 if(!append_string(ctx, buf))
486                     return E_OUTOFMEMORY;
487             }else {
488                 if(!append_char(ctx, *ptr))
489                     return E_OUTOFMEMORY;
490             }
491         }
492         ptr++;
493     }
494 
495     return append_char(ctx, '"') ? S_OK : E_OUTOFMEMORY;
496 }
497 
498 static inline BOOL is_callable(jsdisp_t *obj)
499 {
500     return is_class(obj, JSCLASS_FUNCTION);
501 }
502 
503 static HRESULT stringify(stringify_ctx_t *ctx, jsval_t val);
504 
505 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation JA) */
506 static HRESULT stringify_array(stringify_ctx_t *ctx, jsdisp_t *obj)
507 {
508     unsigned length, i, j;
509     jsval_t val;
510     HRESULT hres;
511 
512     if(is_on_stack(ctx, obj)) {
513         FIXME("Found a cycle\n");
514         return E_FAIL;
515     }
516 
517     if(!stringify_push_obj(ctx, obj))
518         return E_OUTOFMEMORY;
519 
520     if(!append_char(ctx, '['))
521         return E_OUTOFMEMORY;
522 
523     length = array_get_length(obj);
524 
525     for(i=0; i < length; i++) {
526         if(i && !append_char(ctx, ','))
527             return E_OUTOFMEMORY;
528 
529         if(*ctx->gap) {
530             if(!append_char(ctx, '\n'))
531                 return E_OUTOFMEMORY;
532 
533             for(j=0; j < ctx->stack_top; j++) {
534                 if(!append_string(ctx, ctx->gap))
535                     return E_OUTOFMEMORY;
536             }
537         }
538 
539         hres = jsdisp_get_idx(obj, i, &val);
540         if(SUCCEEDED(hres)) {
541             hres = stringify(ctx, val);
542             if(FAILED(hres))
543                 return hres;
544             if(hres == S_FALSE && !append_string(ctx, nullW))
545                 return E_OUTOFMEMORY;
546         }else if(hres == DISP_E_UNKNOWNNAME) {
547             if(!append_string(ctx, nullW))
548                 return E_OUTOFMEMORY;
549         }else {
550             return hres;
551         }
552     }
553 
554     if((length && *ctx->gap && !append_char(ctx, '\n')) || !append_char(ctx, ']'))
555         return E_OUTOFMEMORY;
556 
557     stringify_pop_obj(ctx);
558     return S_OK;
559 }
560 
561 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation JO) */
562 static HRESULT stringify_object(stringify_ctx_t *ctx, jsdisp_t *obj)
563 {
564     DISPID dispid = DISPID_STARTENUM;
565     jsval_t val = jsval_undefined();
566     unsigned prop_cnt = 0, i;
567     size_t stepback;
568     BSTR prop_name;
569     HRESULT hres;
570 
571     if(is_on_stack(ctx, obj)) {
572         FIXME("Found a cycle\n");
573         return E_FAIL;
574     }
575 
576     if(!stringify_push_obj(ctx, obj))
577         return E_OUTOFMEMORY;
578 
579     if(!append_char(ctx, '{'))
580         return E_OUTOFMEMORY;
581 
582     while((hres = IDispatchEx_GetNextDispID(&obj->IDispatchEx_iface, fdexEnumDefault, dispid, &dispid)) == S_OK) {
583         jsval_release(val);
584         hres = jsdisp_propget(obj, dispid, &val);
585         if(FAILED(hres))
586             return hres;
587 
588         if(is_undefined(val))
589             continue;
590 
591         stepback = ctx->buf_len;
592 
593         if(prop_cnt && !append_char(ctx, ',')) {
594             hres = E_OUTOFMEMORY;
595             break;
596         }
597 
598         if(*ctx->gap) {
599             if(!append_char(ctx, '\n')) {
600                 hres = E_OUTOFMEMORY;
601                 break;
602             }
603 
604             for(i=0; i < ctx->stack_top; i++) {
605                 if(!append_string(ctx, ctx->gap)) {
606                     hres = E_OUTOFMEMORY;
607                     break;
608                 }
609             }
610         }
611 
612         hres = IDispatchEx_GetMemberName(&obj->IDispatchEx_iface, dispid, &prop_name);
613         if(FAILED(hres))
614             break;
615 
616         hres = json_quote(ctx, prop_name, SysStringLen(prop_name));
617         SysFreeString(prop_name);
618         if(FAILED(hres))
619             break;
620 
621         if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) {
622             hres = E_OUTOFMEMORY;
623             break;
624         }
625 
626         hres = stringify(ctx, val);
627         if(FAILED(hres))
628             break;
629 
630         if(hres == S_FALSE) {
631             ctx->buf_len = stepback;
632             continue;
633         }
634 
635         prop_cnt++;
636     }
637     jsval_release(val);
638     if(FAILED(hres))
639         return hres;
640 
641     if(prop_cnt && *ctx->gap) {
642         if(!append_char(ctx, '\n'))
643             return E_OUTOFMEMORY;
644 
645         for(i=1; i < ctx->stack_top; i++) {
646             if(!append_string(ctx, ctx->gap)) {
647                 hres = E_OUTOFMEMORY;
648                 break;
649             }
650         }
651     }
652 
653     if(!append_char(ctx, '}'))
654         return E_OUTOFMEMORY;
655 
656     stringify_pop_obj(ctx);
657     return S_OK;
658 }
659 
660 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation Str) */
661 static HRESULT stringify(stringify_ctx_t *ctx, jsval_t val)
662 {
663     jsval_t value;
664     HRESULT hres;
665 
666     if(is_object_instance(val) && get_object(val)) {
667         jsdisp_t *obj;
668         DISPID id;
669 
670         obj = iface_to_jsdisp(get_object(val));
671         if(!obj)
672             return S_FALSE;
673 
674         hres = jsdisp_get_id(obj, toJSONW, 0, &id);
675         jsdisp_release(obj);
676         if(hres == S_OK)
677             FIXME("Use toJSON.\n");
678     }
679 
680     /* FIXME: Support replacer replacer. */
681 
682     hres = maybe_to_primitive(ctx->ctx, val, &value);
683     if(FAILED(hres))
684         return hres;
685 
686     switch(jsval_type(value)) {
687     case JSV_NULL:
688         if(!append_string(ctx, nullW))
689             hres = E_OUTOFMEMORY;
690         break;
691     case JSV_BOOL:
692         if(!append_string(ctx, get_bool(value) ? trueW : falseW))
693             hres = E_OUTOFMEMORY;
694         break;
695     case JSV_STRING: {
696         jsstr_t *str = get_string(value);
697         const WCHAR *ptr = jsstr_flatten(str);
698         if(ptr)
699             hres = json_quote(ctx, ptr, jsstr_length(str));
700         else
701             hres = E_OUTOFMEMORY;
702         break;
703     }
704     case JSV_NUMBER: {
705         double n = get_number(value);
706         if(is_finite(n)) {
707             const WCHAR *ptr;
708             jsstr_t *str;
709 
710             /* FIXME: Optimize. There is no need for jsstr_t here. */
711             hres = double_to_string(n, &str);
712             if(FAILED(hres))
713                 break;
714 
715             ptr = jsstr_flatten(str);
716             assert(ptr != NULL);
717             hres = ptr && !append_string_len(ctx, ptr, jsstr_length(str)) ? E_OUTOFMEMORY : S_OK;
718             jsstr_release(str);
719         }else {
720             if(!append_string(ctx, nullW))
721                 hres = E_OUTOFMEMORY;
722         }
723         break;
724     }
725     case JSV_OBJECT: {
726         jsdisp_t *obj;
727 
728         obj = iface_to_jsdisp(get_object(value));
729         if(!obj) {
730             hres = S_FALSE;
731             break;
732         }
733 
734         if(!is_callable(obj))
735             hres = is_class(obj, JSCLASS_ARRAY) ? stringify_array(ctx, obj) : stringify_object(ctx, obj);
736         else
737             hres = S_FALSE;
738 
739         jsdisp_release(obj);
740         break;
741     }
742     case JSV_UNDEFINED:
743         hres = S_FALSE;
744         break;
745     case JSV_VARIANT:
746         FIXME("VARIANT\n");
747         hres = E_NOTIMPL;
748         break;
749     }
750 
751     jsval_release(value);
752     return hres;
753 }
754 
755 /* ECMA-262 5.1 Edition    15.12.3 */
756 static HRESULT JSON_stringify(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
757 {
758     stringify_ctx_t stringify_ctx = {ctx, NULL,0,0, NULL,0,0, {0}};
759     HRESULT hres;
760 
761     TRACE("\n");
762 
763     if(!argc) {
764         if(r)
765             *r = jsval_undefined();
766         return S_OK;
767     }
768 
769     if(argc >= 2 && is_object_instance(argv[1])) {
770         FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv[1]));
771         return E_NOTIMPL;
772     }
773 
774     if(argc >= 3) {
775         jsval_t space_val;
776 
777         hres = maybe_to_primitive(ctx, argv[2], &space_val);
778         if(FAILED(hres))
779             return hres;
780 
781         if(is_number(space_val)) {
782             double n = get_number(space_val);
783             if(n >= 1) {
784                 int i, len;
785                 if(n > 10)
786                     n = 10;
787                 len = floor(n);
788                 for(i=0; i < len; i++)
789                     stringify_ctx.gap[i] = ' ';
790                 stringify_ctx.gap[len] = 0;
791             }
792         }else if(is_string(space_val)) {
793             jsstr_t *space_str = get_string(space_val);
794             size_t len = jsstr_length(space_str);
795             if(len > 10)
796                 len = 10;
797             jsstr_extract(space_str, 0, len, stringify_ctx.gap);
798         }
799 
800         jsval_release(space_val);
801     }
802 
803     hres = stringify(&stringify_ctx, argv[0]);
804     if(SUCCEEDED(hres) && r) {
805         assert(!stringify_ctx.stack_top);
806 
807         if(hres == S_OK) {
808             jsstr_t *ret = jsstr_alloc_len(stringify_ctx.buf, stringify_ctx.buf_len);
809             if(ret)
810                 *r = jsval_string(ret);
811             else
812                 hres = E_OUTOFMEMORY;
813         }else {
814             *r = jsval_undefined();
815         }
816     }
817 
818     heap_free(stringify_ctx.buf);
819     heap_free(stringify_ctx.stack);
820     return hres;
821 }
822 
823 static const builtin_prop_t JSON_props[] = {
824     {parseW,     JSON_parse,     PROPF_METHOD|2},
825     {stringifyW, JSON_stringify, PROPF_METHOD|3}
826 };
827 
828 static const builtin_info_t JSON_info = {
829     JSCLASS_JSON,
830     {NULL, NULL, 0},
831     ARRAY_SIZE(JSON_props),
832     JSON_props,
833     NULL,
834     NULL
835 };
836 
837 HRESULT create_json(script_ctx_t *ctx, jsdisp_t **ret)
838 {
839     jsdisp_t *json;
840     HRESULT hres;
841 
842     json = heap_alloc_zero(sizeof(*json));
843     if(!json)
844         return E_OUTOFMEMORY;
845 
846     hres = init_dispex_from_constr(json, ctx, &JSON_info, ctx->object_constr);
847     if(FAILED(hres)) {
848         heap_free(json);
849         return hres;
850     }
851 
852     *ret = json;
853     return S_OK;
854 }
855