/[jscoverage]/trunk/js/json.cpp
ViewVC logotype

Diff of /trunk/js/json.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 399 by siliconforks, Tue Dec 9 03:37:47 2008 UTC revision 460 by siliconforks, Sat Sep 26 23:15:22 2009 UTC
# Line 1  Line 1 
1  /* ***** BEGIN LICENSE BLOCK *****  /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2     * vim: set ts=8 sw=4 et tw=99:
3     *
4     * ***** BEGIN LICENSE BLOCK *****
5   * Version: MPL 1.1/GPL 2.0/LGPL 2.1   * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6   *   *
7   * The contents of this file are subject to the Mozilla Public License Version   * The contents of this file are subject to the Mozilla Public License Version
# Line 35  Line 38 
38   *   *
39   * ***** END LICENSE BLOCK ***** */   * ***** END LICENSE BLOCK ***** */
40    
41    #include <string.h>     /* memset */
42  #include "jsapi.h"  #include "jsapi.h"
43  #include "jsarena.h"  #include "jsarena.h"
44  #include "jsarray.h"  #include "jsarray.h"
# Line 43  Line 46 
46  #include "jsbool.h"  #include "jsbool.h"
47  #include "jscntxt.h"  #include "jscntxt.h"
48  #include "jsdtoa.h"  #include "jsdtoa.h"
49    #include "jsfun.h"
50  #include "jsinterp.h"  #include "jsinterp.h"
51  #include "jsiter.h"  #include "jsiter.h"
52  #include "jsnum.h"  #include "jsnum.h"
# Line 52  Line 56 
56  #include "jsstr.h"  #include "jsstr.h"
57  #include "jstypes.h"  #include "jstypes.h"
58  #include "jsutil.h"  #include "jsutil.h"
59    #include "jsxml.h"
60    
61  #include "json.h"  #include "json.h"
62    
# Line 68  Line 73 
73  {  {
74      JSString *s = NULL;      JSString *s = NULL;
75      jsval *argv = vp + 2;      jsval *argv = vp + 2;
76        jsval reviver = JSVAL_NULL;
77      // Must throw an Error if there isn't a first arg      JSAutoTempValueRooter(cx, 1, &reviver);
78      if (!JS_ConvertArguments(cx, argc, argv, "S", &s))      
79        if (!JS_ConvertArguments(cx, argc, argv, "S / v", &s, &reviver))
80          return JS_FALSE;          return JS_FALSE;
81    
   
82      JSONParser *jp = js_BeginJSONParse(cx, vp);      JSONParser *jp = js_BeginJSONParse(cx, vp);
83      JSBool ok = jp != NULL;      JSBool ok = jp != NULL;
   
84      if (ok) {      if (ok) {
85          ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));          ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));
86          ok &= js_FinishJSONParse(cx, jp);          ok &= js_FinishJSONParse(cx, jp, reviver);
87      }      }
88    
     if (!ok)  
         JS_ReportError(cx, "Error parsing JSON");  
   
89      return ok;      return ok;
90  }  }
91    
92  class StringifyClosure : JSAutoTempValueRooter  class WriterContext
93  {  {
94  public:  public:
95      StringifyClosure(JSContext *cx, size_t len, jsval *vec)      WriterContext(JSContext *cx) : cx(cx), didWrite(JS_FALSE)
         : JSAutoTempValueRooter(cx, len, vec), cx(cx), s(vec)  
96      {      {
97            js_InitStringBuffer(&sb);
98      }      }
99    
100        ~WriterContext()
101        {
102            js_FinishStringBuffer(&sb);
103        }
104    
105        JSStringBuffer sb;
106      JSContext *cx;      JSContext *cx;
107      jsval *s;      JSBool didWrite;
108  };  };
109    
110  static JSBool  static JSBool
111  WriteCallback(const jschar *buf, uint32 len, void *data)  WriteCallback(const jschar *buf, uint32 len, void *data)
112  {  {
113      StringifyClosure *sc = static_cast<StringifyClosure*>(data);      WriterContext *wc = static_cast<WriterContext*>(data);
114      JSString *s1 = JSVAL_TO_STRING(sc->s[0]);      wc->didWrite = JS_TRUE;
     JSString *s2 = js_NewStringCopyN(sc->cx, buf, len);  
     if (!s2)  
         return JS_FALSE;  
   
     sc->s[1] = STRING_TO_JSVAL(s2);  
115    
116      s1 = js_ConcatStrings(sc->cx, s1, s2);      js_AppendUCString(&wc->sb, buf, len);
117      if (!s1)      if (!STRING_BUFFER_OK(&wc->sb)) {
118            JS_ReportOutOfMemory(wc->cx);
119          return JS_FALSE;          return JS_FALSE;
120        }
     sc->s[0] = STRING_TO_JSVAL(s1);  
     sc->s[1] = JSVAL_VOID;  
121    
122      return JS_TRUE;      return JS_TRUE;
123  }  }
# Line 124  Line 125 
125  JSBool  JSBool
126  js_json_stringify(JSContext *cx, uintN argc, jsval *vp)  js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
127  {  {
     JSObject *obj;  
128      jsval *argv = vp + 2;      jsval *argv = vp + 2;
129            JSObject *replacer = NULL;
130        jsval space = JSVAL_NULL;
131        JSAutoTempValueRooter(cx, replacer);
132        JSAutoTempValueRooter(cx, 1, &space);
133    
134      // Must throw an Error if there isn't a first arg      // Must throw an Error if there isn't a first arg
135      if (!JS_ConvertArguments(cx, argc, argv, "o", &obj))      if (!JS_ConvertArguments(cx, argc, argv, "v / o v", vp, &replacer, &space))
136          return JS_FALSE;          return JS_FALSE;
137    
138      // Only use objects and arrays as the root for now      WriterContext wc(cx);
139      *vp = OBJECT_TO_JSVAL(obj);  
140            if (!js_Stringify(cx, vp, replacer, space, &WriteCallback, &wc))
     JSBool ok = js_TryJSON(cx, vp);  
     JSType type;  
     if (!ok ||  
         JSVAL_IS_PRIMITIVE(*vp) ||  
         ((type = JS_TypeOfValue(cx, *vp)) == JSTYPE_FUNCTION ||  
         type == JSTYPE_XML)) {  
         JS_ReportError(cx, "Invalid argument");  
141          return JS_FALSE;          return JS_FALSE;
     }  
       
     JSString *s = JS_NewStringCopyN(cx, "", 0);  
     if (!s)  
         ok = JS_FALSE;  
142    
143      if (ok) {      // XXX This can never happen to nsJSON.cpp, but the JSON object
144          jsval vec[2] = {STRING_TO_JSVAL(s), JSVAL_VOID};      // needs to support returning undefined. So this is a little awkward
145          StringifyClosure sc(cx, 2, vec);      // for the API, because we want to support streaming writers.
146          JSAutoTempValueRooter resultTvr(cx, 1, sc.s);      if (wc.didWrite) {
147          ok = js_Stringify(cx, vp, NULL, &WriteCallback, &sc, 0);          JSStringBuffer *sb = &wc.sb;
148          *vp = *sc.s;          JSString *s = JS_NewUCStringCopyN(cx, sb->base, STRING_BUFFER_OFFSET(sb));
149            if (!s)
150                return JS_FALSE;
151            *vp = STRING_TO_JSVAL(s);
152        } else {
153            *vp = JSVAL_VOID;
154      }      }
155    
156      return ok;      return JS_TRUE;
157  }  }
158    
159  JSBool  JSBool
# Line 164  Line 161 
161  {  {
162      // Checks whether the return value implements toJSON()      // Checks whether the return value implements toJSON()
163      JSBool ok = JS_TRUE;      JSBool ok = JS_TRUE;
164        
165      if (!JSVAL_IS_PRIMITIVE(*vp)) {      if (!JSVAL_IS_PRIMITIVE(*vp)) {
166          JSObject *obj = JSVAL_TO_OBJECT(*vp);          JSObject *obj = JSVAL_TO_OBJECT(*vp);
167          ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp);          ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp);
168      }      }
169        
170      return ok;      return ok;
171  }  }
172    
# Line 177  Line 174 
174  static const jschar quote = jschar('"');  static const jschar quote = jschar('"');
175  static const jschar backslash = jschar('\\');  static const jschar backslash = jschar('\\');
176  static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};  static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};
177    static const jschar null_ucstr[] = {'n', 'u', 'l', 'l'};
178    static const jschar true_ucstr[] = {'t', 'r', 'u', 'e'};
179    static const jschar false_ucstr[] = {'f', 'a', 'l', 's', 'e'};
180    
181  static JSBool  static JSBool
182  write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)  write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)
# Line 212  Line 212 
212      if (mark < len && !callback(&buf[mark], len - mark, data))      if (mark < len && !callback(&buf[mark], len - mark, data))
213          return JS_FALSE;          return JS_FALSE;
214    
215      if (!callback(&quote, 1, data))      return callback(&quote, 1, data);
216          return JS_FALSE;  }
217    
218    class StringifyContext
219    {
220    public:
221        StringifyContext(JSONWriteCallback callback, JSObject *replacer, void *data)
222        : callback(callback), replacer(replacer), data(data), depth(0)
223        {
224            js_InitStringBuffer(&gap);
225        }
226    
227        ~StringifyContext()
228        {
229            if (STRING_BUFFER_OK(&gap))
230                js_FinishStringBuffer(&gap);
231        }
232    
233        JSONWriteCallback callback;
234        JSStringBuffer gap;
235        JSObject *replacer;
236        void *data;
237        uint32 depth;
238    };
239    
240    static JSBool CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder,
241                                       StringifyContext *scx, jsval *vp);
242    static JSBool Str(JSContext *cx, jsid id, JSObject *holder,
243                      StringifyContext *scx, jsval *vp, bool callReplacer = true);
244    
245    static JSBool
246    WriteIndent(JSContext *cx, StringifyContext *scx, uint32 limit)
247    {
248        if (STRING_BUFFER_OFFSET(&scx->gap) > 0) {
249            jschar c = jschar('\n');
250            if (!scx->callback(&c, 1, scx->data))
251                return JS_FALSE;
252            for (uint32 i = 0; i < limit; i++) {
253                if (!scx->callback(scx->gap.base, STRING_BUFFER_OFFSET(&scx->gap), scx->data))
254                    return JS_FALSE;
255            }
256        }
257    
258      return JS_TRUE;      return JS_TRUE;
259  }  }
260    
261  JSBool  static JSBool
262  js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,  JO(JSContext *cx, jsval *vp, StringifyContext *scx)
              JSONWriteCallback callback, void *data, uint32 depth)  
263  {  {
     if (depth > JSON_MAX_DEPTH)  
         return JS_FALSE; /* encoding error */  
   
     JSBool ok = JS_TRUE;  
264      JSObject *obj = JSVAL_TO_OBJECT(*vp);      JSObject *obj = JSVAL_TO_OBJECT(*vp);
265      JSBool isArray = JS_IsArrayObject(cx, obj);  
266      jschar output = jschar(isArray ? '[' : '{');      jschar c = jschar('{');
267      if (!callback(&output, 1, data))      if (!scx->callback(&c, 1, scx->data))
268          return JS_FALSE;          return JS_FALSE;
269    
270        jsval vec[3] = {JSVAL_NULL, JSVAL_NULL, JSVAL_NULL};
271        JSAutoTempValueRooter tvr(cx, 3, vec);
272        jsval& key = vec[0];
273        jsval& outputValue = vec[1];
274    
275      JSObject *iterObj = NULL;      JSObject *iterObj = NULL;
276      jsint i = 0;      jsval *keySource = vp;
277      jsuint length = 0;      bool usingWhitelist = false;
278    
279      if (isArray) {      // if the replacer is an array, we use the keys from it
280          if (!js_GetLengthProperty(cx, obj, &length))      if (scx->replacer && JS_IsArrayObject(cx, scx->replacer)) {
281              return JS_FALSE;          usingWhitelist = true;
282      } else {          vec[2] = OBJECT_TO_JSVAL(scx->replacer);
283          if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp))          keySource = &vec[2];
             return JS_FALSE;  
         iterObj = JSVAL_TO_OBJECT(*vp);  
284      }      }
285    
286      jsval outputValue = JSVAL_VOID;      if (!js_ValueToIterator(cx, JSITER_ENUMERATE, keySource))
287      JSAutoTempValueRooter tvr(cx, 1, &outputValue);          return JS_FALSE;
288        iterObj = JSVAL_TO_OBJECT(*keySource);
289    
     jsval key;  
290      JSBool memberWritten = JS_FALSE;      JSBool memberWritten = JS_FALSE;
291        JSBool ok;
292    
293      do {      do {
294          outputValue = JSVAL_VOID;          outputValue = JSVAL_VOID;
295            ok = js_CallIteratorNext(cx, iterObj, &key);
296            if (!ok)
297                break;
298            if (key == JSVAL_HOLE)
299                break;
300    
301          if (isArray) {          jsuint index = 0;
302              if ((jsuint)i >= length)          if (usingWhitelist) {
303                  break;              // skip non-index properties
304              ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue);              if (!js_IdIsIndex(key, &index))
305              i++;                  continue;
306    
307                jsval newKey;
308                if (!OBJ_GET_PROPERTY(cx, scx->replacer, key, &newKey))
309                    return JS_FALSE;
310                key = newKey;
311            }
312    
313            JSString *ks;
314            if (JSVAL_IS_STRING(key)) {
315                ks = JSVAL_TO_STRING(key);
316          } else {          } else {
317              ok = js_CallIteratorNext(cx, iterObj, &key);              ks = js_ValueToString(cx, key);
318              if (!ok)              if (!ks) {
319                  break;                  ok = JS_FALSE;
             if (key == JSVAL_HOLE)  
320                  break;                  break;
   
             JSString *ks;  
             if (JSVAL_IS_STRING(key)) {  
                 ks = JSVAL_TO_STRING(key);  
             } else {  
                 ks = js_ValueToString(cx, key);  
                 if (!ks) {  
                     ok = JS_FALSE;  
                     break;  
                 }  
321              }              }
322            }
323            JSAutoTempValueRooter keyStringRoot(cx, ks);
324    
325              ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),          // Don't include prototype properties, since this operation is
326                                    JS_GetStringLength(ks), &outputValue);          // supposed to be implemented as if by ES3.1 Object.keys()
327            jsid id;
328            jsval v = JS_FALSE;
329            if (!js_ValueToStringId(cx, STRING_TO_JSVAL(ks), &id) ||
330                !js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &v)) {
331                ok = JS_FALSE;
332                break;
333          }          }
334    
335            if (v != JSVAL_TRUE)
336                continue;
337    
338            ok = JS_GetPropertyById(cx, obj, id, &outputValue);
339          if (!ok)          if (!ok)
340              break;              break;
341    
342          // if this is an array, holes are transmitted as null          if (JSVAL_IS_OBJECT(outputValue)) {
         if (isArray && outputValue == JSVAL_VOID) {  
             outputValue = JSVAL_NULL;  
         } else if (JSVAL_IS_OBJECT(outputValue)) {  
343              ok = js_TryJSON(cx, &outputValue);              ok = js_TryJSON(cx, &outputValue);
344              if (!ok)              if (!ok)
345                  break;                  break;
346          }          }
347    
348          // elide undefined values          // call this here, so we don't write out keys if the replacer function
349          if (outputValue == JSVAL_VOID)          // wants to elide the value.
350            if (!CallReplacerFunction(cx, id, obj, scx, &outputValue))
351                return JS_FALSE;
352    
353            JSType type = JS_TypeOfValue(cx, outputValue);
354    
355            // elide undefined values and functions and XML
356            if (outputValue == JSVAL_VOID || type == JSTYPE_FUNCTION || type == JSTYPE_XML)
357              continue;              continue;
358    
359          // output a comma unless this is the first member to write          // output a comma unless this is the first member to write
360          if (memberWritten) {          if (memberWritten) {
361              output = jschar(',');              c = jschar(',');
362              ok = callback(&output, 1, data);              ok = scx->callback(&c, 1, scx->data);
363          if (!ok)              if (!ok)
364                  break;                  break;
365          }          }
366          memberWritten = JS_TRUE;          memberWritten = JS_TRUE;
367    
368          JSType type = JS_TypeOfValue(cx, outputValue);          if (!WriteIndent(cx, scx, scx->depth))
369                return JS_FALSE;
370    
371          // Can't encode these types, so drop them          // Be careful below, this string is weakly rooted
372          if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)          JSString *s = js_ValueToString(cx, key);
373            if (!s) {
374                ok = JS_FALSE;
375              break;              break;
376            }
377    
378          // Be careful below, this string is weakly rooted.          ok = write_string(cx, scx->callback, scx->data, JS_GetStringChars(s), JS_GetStringLength(s));
379          JSString *s;          if (!ok)
380                break;
381    
382          // If this isn't an array, we need to output a key          c = jschar(':');
383          if (!isArray) {          ok = scx->callback(&c, 1, scx->data);
384              s = js_ValueToString(cx, key);          if (!ok)
385              if (!s) {              break;
                 ok = JS_FALSE;  
                 break;  
             }  
386    
387              ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));          ok = Str(cx, id, obj, scx, &outputValue, false);
388              if (!ok)          if (!ok)
389                  break;              break;
390    
391              output = jschar(':');      } while (ok);
392              ok = callback(&output, 1, data);  
393              if (!ok)      if (iterObj) {
394                  break;          // Always close the iterator, but make sure not to stomp on OK
395            JS_ASSERT(OBJECT_TO_JSVAL(iterObj) == *keySource);
396            ok &= js_CloseIterator(cx, *keySource);
397        }
398    
399        if (!ok)
400            return JS_FALSE;
401    
402        if (memberWritten && !WriteIndent(cx, scx, scx->depth - 1))
403            return JS_FALSE;
404    
405        c = jschar('}');
406    
407        return scx->callback(&c, 1, scx->data);
408    }
409    
410    static JSBool
411    JA(JSContext *cx, jsval *vp, StringifyContext *scx)
412    {
413        JSObject *obj = JSVAL_TO_OBJECT(*vp);
414    
415        jschar c = jschar('[');
416        if (!scx->callback(&c, 1, scx->data))
417            return JS_FALSE;
418    
419        jsuint length;
420        if (!js_GetLengthProperty(cx, obj, &length))
421            return JS_FALSE;
422    
423        jsval outputValue = JSVAL_NULL;
424        JSAutoTempValueRooter tvr(cx, 1, &outputValue);
425    
426        jsid id;
427        jsuint i;
428        for (i = 0; i < length; i++) {
429            id = INT_TO_JSID(i);
430    
431            if (!OBJ_GET_PROPERTY(cx, obj, id, &outputValue))
432                return JS_FALSE;
433    
434            if (!Str(cx, id, obj, scx, &outputValue))
435                return JS_FALSE;
436    
437            if (outputValue == JSVAL_VOID) {
438                if (!scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data))
439                    return JS_FALSE;
440          }          }
441    
442          if (!JSVAL_IS_PRIMITIVE(outputValue)) {          if (i < length - 1) {
443              // recurse              c = jschar(',');
444              ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1);              if (!scx->callback(&c, 1, scx->data))
445          } else {                  return JS_FALSE;
446              JSString *outputString;              if (!WriteIndent(cx, scx, scx->depth))
447              s = js_ValueToString(cx, outputValue);                  return JS_FALSE;
448              if (!s) {          }
449                  ok = JS_FALSE;      }
                 break;  
             }  
450    
451              if (type == JSTYPE_STRING) {      if (length != 0 && !WriteIndent(cx, scx, scx->depth - 1))
452                  ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));          return JS_FALSE;
                 if (!ok)  
                     break;  
453    
454                  continue;      c = jschar(']');
             }  
455    
456              if (type == JSTYPE_NUMBER) {      return scx->callback(&c, 1, scx->data);
457                  if (JSVAL_IS_DOUBLE(outputValue)) {  }
                     jsdouble d = *JSVAL_TO_DOUBLE(outputValue);  
                     if (!JSDOUBLE_IS_FINITE(d))  
                         outputString = JS_NewStringCopyN(cx, "null", 4);  
                     else  
                         outputString = s;  
                 } else {  
                     outputString = s;  
                 }  
             } else if (type == JSTYPE_BOOLEAN) {  
                 outputString = s;  
             } else if (JSVAL_IS_NULL(outputValue)) {  
                 outputString = JS_NewStringCopyN(cx, "null", 4);  
             } else {  
                 ok = JS_FALSE; // encoding error  
                 break;  
             }  
458    
459              if (!outputString) {  static JSBool
460                  ok = JS_FALSE;  CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, jsval *vp)
461                  break;  {
462              }      if (scx->replacer && js_IsCallable(scx->replacer, cx)) {
463            jsval vec[2] = {ID_TO_VALUE(id), *vp};
464            if (!JS_CallFunctionValue(cx, holder, OBJECT_TO_JSVAL(scx->replacer), 2, vec, vp))
465                return JS_FALSE;
466        }
467    
468        return JS_TRUE;
469    }
470    
471    static JSBool
472    Str(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, jsval *vp, bool callReplacer)
473    {
474        JS_CHECK_RECURSION(cx, return JS_FALSE);
475    
476        if (!OBJ_GET_PROPERTY(cx, holder, id, vp))
477            return JS_FALSE;
478    
479        if (!JSVAL_IS_PRIMITIVE(*vp) && !js_TryJSON(cx, vp))
480            return JS_FALSE;
481    
482        if (callReplacer && !CallReplacerFunction(cx, id, holder, scx, vp))
483            return JS_FALSE;
484    
485        // catches string and number objects with no toJSON
486        if (!JSVAL_IS_PRIMITIVE(*vp)) {
487            JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(*vp));
488            if (clasp == &js_StringClass || clasp == &js_NumberClass)
489                *vp = JSVAL_TO_OBJECT(*vp)->fslots[JSSLOT_PRIVATE];
490        }
491    
492        if (JSVAL_IS_STRING(*vp)) {
493            JSString *s = JSVAL_TO_STRING(*vp);
494            return write_string(cx, scx->callback, scx->data, JS_GetStringChars(s), JS_GetStringLength(s));
495        }
496    
497        if (JSVAL_IS_NULL(*vp)) {
498            return scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data);
499        }
500    
501              ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);      if (JSVAL_IS_BOOLEAN(*vp)) {
502            uint32 len = JS_ARRAY_LENGTH(true_ucstr);
503            const jschar *chars = true_ucstr;
504            JSBool b = JSVAL_TO_BOOLEAN(*vp);
505    
506            if (!b) {
507                chars = false_ucstr;
508                len = JS_ARRAY_LENGTH(false_ucstr);
509          }          }
     } while (ok);  
510    
511      if (iterObj) {          return scx->callback(chars, len, scx->data);
         // Always close the iterator, but make sure not to stomp on OK  
         ok &= js_CloseIterator(cx, *vp);  
         // encoding error or propagate? FIXME: Bug 408838.  
512      }      }
513    
514      if (!ok) {      if (JSVAL_IS_NUMBER(*vp)) {
515          JS_ReportError(cx, "Error during JSON encoding");          if (JSVAL_IS_DOUBLE(*vp)) {
516                jsdouble d = *JSVAL_TO_DOUBLE(*vp);
517                if (!JSDOUBLE_IS_FINITE(d))
518                    return  scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data);
519            }
520    
521            char numBuf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr;
522            jsdouble d = JSVAL_IS_INT(*vp) ? jsdouble(JSVAL_TO_INT(*vp)) : *JSVAL_TO_DOUBLE(*vp);
523            numStr = JS_dtostr(numBuf, sizeof numBuf, DTOSTR_STANDARD, 0, d);        
524            if (!numStr) {
525                JS_ReportOutOfMemory(cx);
526                return JS_FALSE;
527            }
528    
529            jschar dstr[DTOSTR_STANDARD_BUFFER_SIZE];
530            size_t dbufSize = DTOSTR_STANDARD_BUFFER_SIZE;
531            if (!js_InflateStringToBuffer(cx, numStr, strlen(numStr), dstr, &dbufSize))
532                return JS_FALSE;
533    
534            return scx->callback(dstr, dbufSize, scx->data);
535        }
536    
537        if (JSVAL_IS_OBJECT(*vp) && !VALUE_IS_FUNCTION(cx, *vp) && !VALUE_IS_XML(cx, *vp)) {
538            JSBool ok;
539    
540            scx->depth++;
541            ok = (JS_IsArrayObject(cx, JSVAL_TO_OBJECT(*vp)) ? JA : JO)(cx, vp, scx);
542            scx->depth--;
543    
544            return ok;
545        }
546        
547        *vp = JSVAL_VOID;
548        return JS_TRUE;
549    }
550    
551    static JSBool
552    WriteStringGap(JSContext *cx, jsval space, JSStringBuffer *sb)
553    {
554        JSString *s = js_ValueToString(cx, space);
555        if (!s)
556            return JS_FALSE;
557    
558        js_AppendUCString(sb, JS_GetStringChars(s), JS_GetStringLength(s));
559        if (!STRING_BUFFER_OK(sb)) {
560            JS_ReportOutOfMemory(cx);
561          return JS_FALSE;          return JS_FALSE;
562      }      }
563    
564      output = jschar(isArray ? ']' : '}');      return JS_TRUE;
565      ok = callback(&output, 1, data);  }
566    
567      return ok;  static JSBool
568    InitializeGap(JSContext *cx, jsval space, JSStringBuffer *sb)
569    {
570        if (!JSVAL_IS_PRIMITIVE(space)) {
571            JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(space));
572            if (clasp == &js_StringClass || clasp == &js_NumberClass)
573                return WriteStringGap(cx, space, sb);
574        }
575    
576        if (JSVAL_IS_STRING(space))
577            return WriteStringGap(cx, space, sb);
578    
579        if (JSVAL_IS_NUMBER(space)) {
580            uint32 i;
581            if (!JS_ValueToECMAUint32(cx, space, &i))
582                return JS_FALSE;
583    
584            js_RepeatChar(sb, jschar(' '), i);
585    
586            if (!STRING_BUFFER_OK(sb)) {
587                JS_ReportOutOfMemory(cx);
588                return JS_FALSE;
589            }
590        }
591    
592        return JS_TRUE;
593    }
594    
595    JSBool
596    js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, jsval space,
597                 JSONWriteCallback callback, void *data)
598    {
599        // XXX stack
600        JSObject *stack = JS_NewArrayObject(cx, 0, NULL);
601        if (!stack)
602            return JS_FALSE;
603    
604        StringifyContext scx(callback, replacer, data);
605        if (!InitializeGap(cx, space, &scx.gap))
606            return JS_FALSE;
607    
608        JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
609        if (!obj)
610            return JS_FALSE;
611    
612        if (!OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom),
613                                 *vp, NULL, NULL, JSPROP_ENUMERATE, NULL)) {
614            return JS_FALSE;
615        }
616    
617        return Str(cx, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), obj, &scx, vp);
618  }  }
619    
620  // helper to determine whether a character could be part of a number  // helper to determine whether a character could be part of a number
# Line 402  Line 623 
623      return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');      return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
624  }  }
625    
626    static JSBool HandleData(JSContext *cx, JSONParser *jp, JSONDataType type);
627    static JSBool PopState(JSContext *cx, JSONParser *jp);
628    
629    static JSBool
630    DestroyIdArrayOnError(JSContext *cx, JSIdArray *ida) {
631        JS_DestroyIdArray(cx, ida);
632        return JS_FALSE;
633    }
634    
635    static JSBool
636    Walk(JSContext *cx, jsid id, JSObject *holder, jsval reviver, jsval *vp)
637    {
638        JS_CHECK_RECURSION(cx, return JS_FALSE);
639        
640        if (!OBJ_GET_PROPERTY(cx, holder, id, vp))
641            return JS_FALSE;
642    
643        JSObject *obj;
644    
645        if (!JSVAL_IS_PRIMITIVE(*vp) && !js_IsCallable(obj = JSVAL_TO_OBJECT(*vp), cx)) {
646            jsval propValue = JSVAL_NULL;
647            JSAutoTempValueRooter tvr(cx, 1, &propValue);
648            
649            if(OBJ_IS_ARRAY(cx, obj)) {
650                jsuint length = 0;
651                if (!js_GetLengthProperty(cx, obj, &length))
652                    return JS_FALSE;
653    
654                for (jsuint i = 0; i < length; i++) {
655                    jsid index;
656                    if (!js_IndexToId(cx, i, &index))
657                        return JS_FALSE;
658    
659                    if (!Walk(cx, index, obj, reviver, &propValue))
660                        return JS_FALSE;
661    
662                    if (!OBJ_DEFINE_PROPERTY(cx, obj, index, propValue,
663                                             NULL, NULL, JSPROP_ENUMERATE, NULL)) {
664                        return JS_FALSE;
665                    }
666                }
667            } else {
668                JSIdArray *ida = JS_Enumerate(cx, obj);
669                if (!ida)
670                    return JS_FALSE;
671    
672                JSAutoTempValueRooter idaroot(cx, JS_ARRAY_LENGTH(ida), (jsval*)ida);
673    
674                for(jsint i = 0; i < ida->length; i++) {
675                    jsid idName = ida->vector[i];
676                    if (!Walk(cx, idName, obj, reviver, &propValue))
677                        return DestroyIdArrayOnError(cx, ida);
678                    if (propValue == JSVAL_VOID) {
679                        if (!js_DeleteProperty(cx, obj, idName, &propValue))
680                            return DestroyIdArrayOnError(cx, ida);
681                    } else {
682                        if (!OBJ_DEFINE_PROPERTY(cx, obj, idName, propValue,
683                                                 NULL, NULL, JSPROP_ENUMERATE, NULL)) {
684                            return DestroyIdArrayOnError(cx, ida);
685                        }
686                    }
687                }
688    
689                JS_DestroyIdArray(cx, ida);
690            }
691        }
692    
693        // return reviver.call(holder, key, value);
694        jsval value = *vp;
695        JSString *key = js_ValueToString(cx, ID_TO_VALUE(id));
696        if (!key)
697            return JS_FALSE;
698    
699        jsval vec[2] = {STRING_TO_JSVAL(key), value};
700        jsval reviverResult;
701        if (!JS_CallFunctionValue(cx, holder, reviver, 2, vec, &reviverResult))
702            return JS_FALSE;
703    
704        *vp = reviverResult;
705    
706        return JS_TRUE;
707    }
708    
709    static JSBool
710    Revive(JSContext *cx, jsval reviver, jsval *vp)
711    {
712        
713        JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
714        if (!obj)
715            return JS_FALSE;
716    
717        jsval v = OBJECT_TO_JSVAL(obj);
718        JSAutoTempValueRooter tvr(cx, 1, &v);
719        if (!OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom),
720                                 *vp, NULL, NULL, JSPROP_ENUMERATE, NULL)) {
721            return JS_FALSE;
722        }
723    
724        return Walk(cx, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), obj, reviver, vp);
725    }
726    
727    #define JSON_INITIAL_BUFSIZE    256
728    
729  JSONParser *  JSONParser *
730  js_BeginJSONParse(JSContext *cx, jsval *rootVal)  js_BeginJSONParse(JSContext *cx, jsval *rootVal)
731  {  {
# Line 415  Line 739 
739      JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));      JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));
740      if (!jp)      if (!jp)
741          return NULL;          return NULL;
742      jp->buffer = NULL;      memset(jp, 0, sizeof *jp);
743    
744      jp->objectStack = arr;      jp->objectStack = arr;
745      if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack"))      if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack"))
746          goto bad;          goto bad;
747    
     jp->hexChar = 0;  
     jp->numHex = 0;  
748      jp->statep = jp->stateStack;      jp->statep = jp->stateStack;
749      *jp->statep = JSON_PARSE_STATE_INIT;      *jp->statep = JSON_PARSE_STATE_INIT;
750      jp->rootVal = rootVal;      jp->rootVal = rootVal;
751    
752      jp->objectKey = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));      js_InitStringBuffer(&jp->objectKey);
753      if (!jp->objectKey)      js_InitStringBuffer(&jp->buffer);
         goto bad;  
     js_InitStringBuffer(jp->objectKey);  
       
     jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));  
     if (!jp->buffer)  
         goto bad;  
     js_InitStringBuffer(jp->buffer);  
754    
755        if (!jp->buffer.grow(&jp->buffer, JSON_INITIAL_BUFSIZE)) {
756            JS_ReportOutOfMemory(cx);
757            goto bad;
758        }
759      return jp;      return jp;
760    
761  bad:  bad:
762      JS_free(cx, jp->buffer);      js_FinishJSONParse(cx, jp, JSVAL_NULL);
     JS_free(cx, jp);  
763      return NULL;      return NULL;
764  }  }
765    
766  JSBool  JSBool
767  js_FinishJSONParse(JSContext *cx, JSONParser *jp)  js_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
768  {  {
769      if (!jp)      if (!jp)
770          return JS_TRUE;          return JS_TRUE;
771    
772      if (jp->objectKey)      JSBool early_ok = JS_TRUE;
773          js_FinishStringBuffer(jp->objectKey);  
774      JS_free(cx, jp->objectKey);      // Check for unprocessed primitives at the root. This doesn't happen for
775        // strings because a closing quote triggers value processing.
776      if (jp->buffer)      if ((jp->statep - jp->stateStack) == 1) {
777          js_FinishStringBuffer(jp->buffer);          if (*jp->statep == JSON_PARSE_STATE_KEYWORD) {
778      JS_free(cx, jp->buffer);              early_ok = HandleData(cx, jp, JSON_DATA_KEYWORD);
779                    if (early_ok)
780      if (!js_RemoveRoot(cx->runtime, &jp->objectStack))                  PopState(cx, jp);
781          return JS_FALSE;          } else if (*jp->statep == JSON_PARSE_STATE_NUMBER) {
782                early_ok = HandleData(cx, jp, JSON_DATA_NUMBER);
783                if (early_ok)
784                    PopState(cx, jp);
785            }
786        }
787    
788        js_FinishStringBuffer(&jp->objectKey);
789        js_FinishStringBuffer(&jp->buffer);
790    
791        /* This internal API is infallible, in spite of its JSBool return type. */
792        js_RemoveRoot(cx->runtime, &jp->objectStack);
793    
794      JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;      JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
795        jsval *vp = jp->rootVal;
796      JS_free(cx, jp);      JS_free(cx, jp);
797    
798        if (!early_ok)
799            return JS_FALSE;
800    
801        if (!ok) {
802            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
803            return JS_FALSE;
804        }
805    
806        if (!JSVAL_IS_PRIMITIVE(reviver) && js_IsCallable(JSVAL_TO_OBJECT(reviver), cx))
807            ok = Revive(cx, reviver, vp);
808    
809      return ok;      return ok;
810  }  }
811    
812  static JSBool  static JSBool
813  PushState(JSONParser *jp, JSONParserState state)  PushState(JSContext *cx, JSONParser *jp, JSONParserState state)
814  {  {
815      if (*jp->statep == JSON_PARSE_STATE_FINISHED)      if (*jp->statep == JSON_PARSE_STATE_FINISHED) {
816          return JS_FALSE; // extra input          // extra input
817            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
818            return JS_FALSE;
819        }
820    
821      jp->statep++;      jp->statep++;
822      if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack))      if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack)) {
823          return JS_FALSE; // too deep          // too deep
824            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
825            return JS_FALSE;
826        }
827    
828      *jp->statep = state;      *jp->statep = state;
829    
# Line 482  Line 831 
831  }  }
832    
833  static JSBool  static JSBool
834  PopState(JSONParser *jp)  PopState(JSContext *cx, JSONParser *jp)
835  {  {
836      jp->statep--;      jp->statep--;
837      if (jp->statep < jp->stateStack) {      if (jp->statep < jp->stateStack) {
838          jp->statep = jp->stateStack;          jp->statep = jp->stateStack;
839            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
840          return JS_FALSE;          return JS_FALSE;
841      }      }
842    
# Line 499  Line 849 
849  static JSBool  static JSBool
850  PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)  PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
851  {  {
     JSAutoTempValueRooter tvr(cx, 1, &value);  
   
852      JSBool ok;      JSBool ok;
853      if (OBJ_IS_ARRAY(cx, parent)) {      if (OBJ_IS_ARRAY(cx, parent)) {
854          jsuint len;          jsuint len;
855          ok = js_GetLengthProperty(cx, parent, &len);          ok = js_GetLengthProperty(cx, parent, &len);
856          if (ok) {          if (ok) {
857              ok = OBJ_DEFINE_PROPERTY(cx, parent, INT_TO_JSID(len), value,              jsid index;
858                if (!js_IndexToId(cx, len, &index))
859                    return JS_FALSE;
860                ok = OBJ_DEFINE_PROPERTY(cx, parent, index, value,
861                                       NULL, NULL, JSPROP_ENUMERATE, NULL);                                       NULL, NULL, JSPROP_ENUMERATE, NULL);
862          }          }
863      } else {      } else {
864          ok = JS_DefineUCProperty(cx, parent, jp->objectKey->base,          ok = JS_DefineUCProperty(cx, parent, jp->objectKey.base,
865                                   STRING_BUFFER_OFFSET(jp->objectKey), value,                                   STRING_BUFFER_OFFSET(&jp->objectKey), value,
866                                   NULL, NULL, JSPROP_ENUMERATE);                                   NULL, NULL, JSPROP_ENUMERATE);
867          js_FinishStringBuffer(jp->objectKey);          js_RewindStringBuffer(&jp->objectKey);
         js_InitStringBuffer(jp->objectKey);  
868      }      }
869    
870      return ok;      return ok;
# Line 526  Line 876 
876      jsuint len;      jsuint len;
877      if (!js_GetLengthProperty(cx, jp->objectStack, &len))      if (!js_GetLengthProperty(cx, jp->objectStack, &len))
878          return JS_FALSE;          return JS_FALSE;
879      if (len >= JSON_MAX_DEPTH)      if (len >= JSON_MAX_DEPTH) {
880          return JS_FALSE; // decoding error          JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
881            return JS_FALSE;
882        }
883    
884      jsval v = OBJECT_TO_JSVAL(obj);      jsval v = OBJECT_TO_JSVAL(obj);
885      JSAutoTempValueRooter tvr(cx, v);      JSAutoTempValueRooter tvr(cx, v);
# Line 549  Line 901 
901    
902      JS_ASSERT(JSVAL_IS_OBJECT(p));      JS_ASSERT(JSVAL_IS_OBJECT(p));
903      JSObject *parent = JSVAL_TO_OBJECT(p);      JSObject *parent = JSVAL_TO_OBJECT(p);
904      if (!PushValue(cx, jp, parent, OBJECT_TO_JSVAL(obj)))      if (!PushValue(cx, jp, parent, v))
905          return JS_FALSE;          return JS_FALSE;
906    
907      // This property must be enumerable to keep the array dense      // This property must be enumerable to keep the array dense
# Line 561  Line 913 
913      return JS_TRUE;      return JS_TRUE;
914  }  }
915    
 static JSObject *  
 GetTopOfObjectStack(JSContext *cx, JSONParser *jp)  
 {  
     jsuint length;  
     if (!js_GetLengthProperty(cx, jp->objectStack, &length))  
         return NULL;  
       
     jsval o;  
     if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(length - 1), &o))  
         return NULL;  
       
     JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));  
     return JSVAL_TO_OBJECT(o);  
 }  
   
916  static JSBool  static JSBool
917  OpenObject(JSContext *cx, JSONParser *jp)  OpenObject(JSContext *cx, JSONParser *jp)
918  {  {
# Line 616  Line 953 
953  }  }
954    
955  static JSBool  static JSBool
956    PushPrimitive(JSContext *cx, JSONParser *jp, jsval value)
957    {
958        JSAutoTempValueRooter tvr(cx, 1, &value);
959    
960        jsuint len;
961        if (!js_GetLengthProperty(cx, jp->objectStack, &len))
962            return JS_FALSE;
963    
964        if (len > 0) {
965            jsval o;
966            if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &o))
967                return JS_FALSE;
968    
969            JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));
970            return PushValue(cx, jp, JSVAL_TO_OBJECT(o), value);
971        }
972    
973        // root value must be primitive
974        *jp->rootVal = value;
975        return JS_TRUE;
976    }
977    
978    static JSBool
979  HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)  HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
980  {  {
981      const jschar *ep;      const jschar *ep;
982      double val;      double val;
983      if (!js_strtod(cx, buf, buf + len, &ep, &val) || ep != buf + len)      if (!js_strtod(cx, buf, buf + len, &ep, &val))
984          return JS_FALSE;          return JS_FALSE;
985        if (ep != buf + len) {
986            // bad number input
987            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
988            return JS_FALSE;
989        }
990    
991      JSBool ok;      jsval numVal;        
992      jsval numVal;      if (!JS_NewNumberValue(cx, val, &numVal))
993      JSObject *obj = GetTopOfObjectStack(cx, jp);          return JS_FALSE;
994      if (obj && JS_NewNumberValue(cx, val, &numVal))          
995          ok = PushValue(cx, jp, obj, numVal);      return PushPrimitive(cx, jp, numVal);
     else  
         ok = JS_FALSE; // decode error  
   
     return ok;  
996  }  }
997    
998  static JSBool  static JSBool
999  HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)  HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
1000  {  {
     JSObject *obj = GetTopOfObjectStack(cx, jp);  
1001      JSString *str = js_NewStringCopyN(cx, buf, len);      JSString *str = js_NewStringCopyN(cx, buf, len);
1002      if (!obj || !str)      if (!str)
1003          return JS_FALSE;          return JS_FALSE;
1004    
1005      return PushValue(cx, jp, obj, STRING_TO_JSVAL(str));      return PushPrimitive(cx, jp, STRING_TO_JSVAL(str));
1006  }  }
1007    
1008  static JSBool  static JSBool
# Line 650  Line 1010 
1010  {  {
1011      jsval keyword;      jsval keyword;
1012      JSTokenType tt = js_CheckKeyword(buf, len);      JSTokenType tt = js_CheckKeyword(buf, len);
1013      if (tt != TOK_PRIMARY)      if (tt != TOK_PRIMARY) {
1014            // bad keyword
1015            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1016          return JS_FALSE;          return JS_FALSE;
1017        }
1018    
1019      if (buf[0] == 'n')      if (buf[0] == 'n') {
1020          keyword = JSVAL_NULL;          keyword = JSVAL_NULL;
1021      else if (buf[0] == 't')      } else if (buf[0] == 't') {
1022          keyword = JSVAL_TRUE;          keyword = JSVAL_TRUE;
1023      else if (buf[0] == 'f')      } else if (buf[0] == 'f') {
1024          keyword = JSVAL_FALSE;          keyword = JSVAL_FALSE;
1025      else      } else {
1026          return JS_FALSE;          JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
   
     JSObject *obj = GetTopOfObjectStack(cx, jp);  
     if (!obj)  
1027          return JS_FALSE;          return JS_FALSE;
1028        }
1029    
1030      return PushValue(cx, jp, obj, keyword);      return PushPrimitive(cx, jp, keyword);
1031  }  }
1032    
1033  static JSBool  static JSBool
1034  HandleData(JSContext *cx, JSONParser *jp, JSONDataType type)  HandleData(JSContext *cx, JSONParser *jp, JSONDataType type)
1035  {  {
1036    JSBool ok = JS_FALSE;      JSBool ok;
   
   if (!STRING_BUFFER_OK(jp->buffer))  
       return JS_FALSE;  
   
   switch (type) {  
     case JSON_DATA_STRING:  
       ok = HandleString(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));  
       break;  
   
     case JSON_DATA_KEYSTRING:  
       js_AppendUCString(jp->objectKey, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));  
       ok = STRING_BUFFER_OK(jp->objectKey);  
       break;  
   
     case JSON_DATA_NUMBER:  
       ok = HandleNumber(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));  
       break;  
   
     case JSON_DATA_KEYWORD:  
       ok = HandleKeyword(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));  
       break;  
1037    
1038      default:      switch (type) {
1039        JS_NOT_REACHED("Should have a JSON data type");        case JSON_DATA_STRING:
1040    }          ok = HandleString(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer));
1041            break;
1042    
1043          case JSON_DATA_KEYSTRING:
1044            js_AppendUCString(&jp->objectKey, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer));
1045            ok = STRING_BUFFER_OK(&jp->objectKey);
1046            if (!ok)
1047                JS_ReportOutOfMemory(cx);
1048            break;
1049    
1050    js_FinishStringBuffer(jp->buffer);        case JSON_DATA_NUMBER:
1051    js_InitStringBuffer(jp->buffer);          ok = HandleNumber(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer));
1052            break;
1053    
1054          default:
1055            JS_ASSERT(type == JSON_DATA_KEYWORD);
1056            ok = HandleKeyword(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer));
1057            break;
1058        }
1059    
1060    return ok;      if (ok) {
1061            ok = STRING_BUFFER_OK(&jp->buffer);
1062            if (ok)
1063                js_RewindStringBuffer(&jp->buffer);
1064            else
1065                JS_ReportOutOfMemory(cx);
1066        }
1067        return ok;
1068  }  }
1069    
1070  JSBool  JSBool
# Line 711  Line 1073 
1073      uint32 i;      uint32 i;
1074    
1075      if (*jp->statep == JSON_PARSE_STATE_INIT) {      if (*jp->statep == JSON_PARSE_STATE_INIT) {
1076          PushState(jp, JSON_PARSE_STATE_OBJECT_VALUE);          PushState(cx, jp, JSON_PARSE_STATE_VALUE);
1077      }      }
1078    
1079      for (i = 0; i < len; i++) {      for (i = 0; i < len; i++) {
1080          jschar c = data[i];          jschar c = data[i];
1081          switch (*jp->statep) {          switch (*jp->statep) {
1082              case JSON_PARSE_STATE_VALUE :            case JSON_PARSE_STATE_VALUE:
1083                  if (c == ']') {              if (c == ']') {
1084                      // empty array                  // empty array
1085                      if (!PopState(jp))                  if (!PopState(cx, jp))
1086                          return JS_FALSE;                      return JS_FALSE;
1087                      if (*jp->statep != JSON_PARSE_STATE_ARRAY)  
1088                          return JS_FALSE; // unexpected char                  if (*jp->statep != JSON_PARSE_STATE_ARRAY) {
1089                      if (!CloseArray(cx, jp) || !PopState(jp))                      JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1090                          return JS_FALSE;                      return JS_FALSE;
                     break;  
                 }  
   
                 if (c == '}') {  
                     // we should only find these in OBJECT_KEY state  
                     return JS_FALSE; // unexpected failure  
                 }  
   
                 if (c == '"') {  
                     *jp->statep = JSON_PARSE_STATE_STRING;  
                     break;  
                 }  
   
                 if (IsNumChar(c)) {  
                     *jp->statep = JSON_PARSE_STATE_NUMBER;  
                     js_AppendChar(jp->buffer, c);  
                     break;  
1091                  }                  }
1092    
1093                  if (JS7_ISLET(c)) {                  if (!CloseArray(cx, jp) || !PopState(cx, jp))
1094                      *jp->statep = JSON_PARSE_STATE_KEYWORD;                      return JS_FALSE;
                     js_AppendChar(jp->buffer, c);  
                     break;  
                 }  
1095    
             // fall through in case the value is an object or array  
             case JSON_PARSE_STATE_OBJECT_VALUE :  
                 if (c == '{') {  
                   *jp->statep = JSON_PARSE_STATE_OBJECT;  
                   if (!OpenObject(cx, jp) || !PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))  
                       return JS_FALSE;  
                 } else if (c == '[') {  
                   *jp->statep = JSON_PARSE_STATE_ARRAY;  
                   if (!OpenArray(cx, jp) || !PushState(jp, JSON_PARSE_STATE_VALUE))  
                       return JS_FALSE;  
                 } else if (!JS_ISXMLSPACE(c)) {  
                   return JS_FALSE; // unexpected  
                 }  
1096                  break;                  break;
1097                }
1098    
1099              case JSON_PARSE_STATE_OBJECT :              if (c == '}') {
1100                  if (c == '}') {                  // we should only find these in OBJECT_KEY state
1101                      if (!CloseObject(cx, jp) || !PopState(jp))                  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1102                          return JS_FALSE;                  return JS_FALSE;
1103                  } else if (c == ',') {              }
                     if (!PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))  
                         return JS_FALSE;  
                 } else if (c == ']' || !JS_ISXMLSPACE(c)) {  
                     return JS_FALSE; // unexpected  
                 }  
                 break;  
1104    
1105              case JSON_PARSE_STATE_ARRAY :              if (c == '"') {
1106                  if (c == ']') {                  *jp->statep = JSON_PARSE_STATE_STRING;
                     if (!CloseArray(cx, jp) || !PopState(jp))  
                         return JS_FALSE;  
                 } else if (c == ',') {  
                     if (!PushState(jp, JSON_PARSE_STATE_VALUE))  
                         return JS_FALSE;  
                 } else if (!JS_ISXMLSPACE(c)) {  
                     return JS_FALSE; // unexpected  
                 }  
1107                  break;                  break;
1108                }
1109    
1110              case JSON_PARSE_STATE_OBJECT_PAIR :              if (IsNumChar(c)) {
1111                  if (c == '"') {                  *jp->statep = JSON_PARSE_STATE_NUMBER;
1112                      // we want to be waiting for a : when the string has been read                  js_FastAppendChar(&jp->buffer, c);
                     *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR;  
                     if (!PushState(jp, JSON_PARSE_STATE_STRING))  
                         return JS_FALSE;  
                 } else if (c == '}') {  
                     // pop off the object pair state and the object state  
                     if (!CloseObject(cx, jp) || !PopState(jp) || !PopState(jp))  
                         return JS_FALSE;  
                 } else if (c == ']' || !JS_ISXMLSPACE(c)) {  
                   return JS_FALSE; // unexpected  
                 }  
1113                  break;                  break;
1114                }
1115    
1116              case JSON_PARSE_STATE_OBJECT_IN_PAIR:              if (JS7_ISLET(c)) {
1117                  if (c == ':') {                  *jp->statep = JSON_PARSE_STATE_KEYWORD;
1118                      *jp->statep = JSON_PARSE_STATE_VALUE;                  js_FastAppendChar(&jp->buffer, c);
                 } else if (!JS_ISXMLSPACE(c)) {  
                     return JS_FALSE; // unexpected  
                 }  
1119                  break;                  break;
1120                }
1121    
1122              case JSON_PARSE_STATE_STRING:            // fall through in case the value is an object or array
1123                  if (c == '"') {            case JSON_PARSE_STATE_OBJECT_VALUE:
1124                      if (!PopState(jp))              if (c == '{') {
1125                          return JS_FALSE;                  *jp->statep = JSON_PARSE_STATE_OBJECT;
1126                      JSONDataType jdt;                  if (!OpenObject(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR))
1127                      if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {                      return JS_FALSE;
1128                          jdt = JSON_DATA_KEYSTRING;              } else if (c == '[') {
1129                      } else {                  *jp->statep = JSON_PARSE_STATE_ARRAY;
1130                          jdt = JSON_DATA_STRING;                  if (!OpenArray(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_VALUE))
1131                      }                      return JS_FALSE;
1132                      if (!HandleData(cx, jp, jdt))              } else if (!JS_ISXMLSPACE(c)) {
1133                          return JS_FALSE;                  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1134                  } else if (c == '\\') {                  return JS_FALSE;
1135                      *jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;              }
1136                  } else {              break;
                     js_AppendChar(jp->buffer, c);  
                 }  
                 break;  
1137    
1138              case JSON_PARSE_STATE_STRING_ESCAPE:            case JSON_PARSE_STATE_OBJECT:
1139                  switch (c) {              if (c == '}') {
1140                      case '"':                  if (!CloseObject(cx, jp) || !PopState(cx, jp))
1141                      case '\\':                      return JS_FALSE;
1142                      case '/':              } else if (c == ',') {
1143                          break;                  if (!PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR))
1144                      case 'b' : c = '\b'; break;                      return JS_FALSE;
1145                      case 'f' : c = '\f'; break;              } else if (c == ']' || !JS_ISXMLSPACE(c)) {
1146                      case 'n' : c = '\n'; break;                  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1147                      case 'r' : c = '\r'; break;                  return JS_FALSE;
1148                      case 't' : c = '\t'; break;              }
1149                      default :              break;
                         if (c == 'u') {  
                             jp->numHex = 0;  
                             jp->hexChar = 0;  
                             *jp->statep = JSON_PARSE_STATE_STRING_HEX;  
                             continue;  
                         } else {  
                             return JS_FALSE; // unexpected  
                         }  
                 }  
1150    
1151                  js_AppendChar(jp->buffer, c);            case JSON_PARSE_STATE_ARRAY :
1152                  *jp->statep = JSON_PARSE_STATE_STRING;              if (c == ']') {
1153                  break;                  if (!CloseArray(cx, jp) || !PopState(cx, jp))
1154                        return JS_FALSE;
1155                } else if (c == ',') {
1156                    if (!PushState(cx, jp, JSON_PARSE_STATE_VALUE))
1157                        return JS_FALSE;
1158                } else if (!JS_ISXMLSPACE(c)) {
1159                    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1160                    return JS_FALSE;
1161                }
1162                break;
1163    
1164              case JSON_PARSE_STATE_STRING_HEX:            case JSON_PARSE_STATE_OBJECT_PAIR :
1165                  if (('0' <= c) && (c <= '9'))              if (c == '"') {
1166                    jp->hexChar = (jp->hexChar << 4) | (c - '0');                  // we want to be waiting for a : when the string has been read
1167                  else if (('a' <= c) && (c <= 'f'))                  *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR;
1168                    jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a);                  if (!PushState(cx, jp, JSON_PARSE_STATE_STRING))
1169                  else if (('A' <= c) && (c <= 'F'))                      return JS_FALSE;
1170                    jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a);              } else if (c == '}') {
1171                  else                  // pop off the object pair state and the object state
1172                    return JS_FALSE; // unexpected                  if (!CloseObject(cx, jp) || !PopState(cx, jp) || !PopState(cx, jp))
1173                        return JS_FALSE;
1174                } else if (c == ']' || !JS_ISXMLSPACE(c)) {
1175                  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1176                  return JS_FALSE;
1177                }
1178                break;
1179    
1180                  if (++(jp->numHex) == 4) {            case JSON_PARSE_STATE_OBJECT_IN_PAIR:
1181                      js_AppendChar(jp->buffer, jp->hexChar);              if (c == ':') {
1182                      jp->hexChar = 0;                  *jp->statep = JSON_PARSE_STATE_VALUE;
1183                      jp->numHex = 0;              } else if (!JS_ISXMLSPACE(c)) {
1184                      *jp->statep = JSON_PARSE_STATE_STRING;                  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1185                  }                  return JS_FALSE;
1186                  break;              }
1187                break;
1188    
1189              case JSON_PARSE_STATE_KEYWORD:            case JSON_PARSE_STATE_STRING:
1190                  if (JS7_ISLET(c)) {              if (c == '"') {
1191                      js_AppendChar(jp->buffer, c);                  if (!PopState(cx, jp))
1192                        return JS_FALSE;
1193                    JSONDataType jdt;
1194                    if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
1195                        jdt = JSON_DATA_KEYSTRING;
1196                  } else {                  } else {
1197                      // this character isn't part of the keyword, process it again                      jdt = JSON_DATA_STRING;
                     i--;  
                     if (!PopState(jp))  
                         return JS_FALSE;  
   
                     if (!HandleData(cx, jp, JSON_DATA_KEYWORD))  
                         return JS_FALSE;  
1198                  }                  }
1199                  break;                  if (!HandleData(cx, jp, jdt))
1200                        return JS_FALSE;
1201                } else if (c == '\\') {
1202                    *jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;
1203                } else {
1204                    js_FastAppendChar(&jp->buffer, c);
1205                }
1206                break;
1207    
1208              case JSON_PARSE_STATE_NUMBER:            case JSON_PARSE_STATE_STRING_ESCAPE:
1209                  if (IsNumChar(c)) {              switch (c) {
1210                      js_AppendChar(jp->buffer, c);                case '"':
1211                  case '\\':
1212                  case '/':
1213                    break;
1214                  case 'b' : c = '\b'; break;
1215                  case 'f' : c = '\f'; break;
1216                  case 'n' : c = '\n'; break;
1217                  case 'r' : c = '\r'; break;
1218                  case 't' : c = '\t'; break;
1219                  default :
1220                    if (c == 'u') {
1221                        jp->numHex = 0;
1222                        jp->hexChar = 0;
1223                        *jp->statep = JSON_PARSE_STATE_STRING_HEX;
1224                        continue;
1225                  } else {                  } else {
1226                      // this character isn't part of the number, process it again                      JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1227                      i--;                      return JS_FALSE;
                     if (!PopState(jp))  
                         return JS_FALSE;  
                     if (!HandleData(cx, jp, JSON_DATA_NUMBER))  
                         return JS_FALSE;  
1228                  }                  }
1229                  break;              }
1230    
1231              case JSON_PARSE_STATE_FINISHED:              js_FastAppendChar(&jp->buffer, c);
1232                  if (!JS_ISXMLSPACE(c))              *jp->statep = JSON_PARSE_STATE_STRING;
1233                    return JS_FALSE; // extra input              break;
1234    
1235                  break;            case JSON_PARSE_STATE_STRING_HEX:
1236                if (('0' <= c) && (c <= '9')) {
1237                    jp->hexChar = (jp->hexChar << 4) | (c - '0');
1238                } else if (('a' <= c) && (c <= 'f')) {
1239                    jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a);
1240                } else if (('A' <= c) && (c <= 'F')) {
1241                    jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a);
1242                } else {
1243                    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1244                    return JS_FALSE;
1245                }
1246                
1247                if (++(jp->numHex) == 4) {
1248                    js_FastAppendChar(&jp->buffer, jp->hexChar);
1249                    jp->hexChar = 0;
1250                    jp->numHex = 0;
1251                    *jp->statep = JSON_PARSE_STATE_STRING;
1252                }
1253                break;
1254    
1255              default:            case JSON_PARSE_STATE_KEYWORD:
1256                  JS_NOT_REACHED("Invalid JSON parser state");              if (JS7_ISLET(c)) {
1257        }                  js_FastAppendChar(&jp->buffer, c);
1258                } else {
1259                    // this character isn't part of the keyword, process it again
1260                    i--;
1261                    if (!PopState(cx, jp))
1262                        return JS_FALSE;
1263                
1264                    if (!HandleData(cx, jp, JSON_DATA_KEYWORD))
1265                        return JS_FALSE;
1266                }
1267                break;
1268    
1269              case JSON_PARSE_STATE_NUMBER:
1270                if (IsNumChar(c)) {
1271                    js_FastAppendChar(&jp->buffer, c);
1272                } else {
1273                    // this character isn't part of the number, process it again
1274                    i--;
1275                    if (!PopState(cx, jp))
1276                        return JS_FALSE;
1277                    if (!HandleData(cx, jp, JSON_DATA_NUMBER))
1278                        return JS_FALSE;
1279                }
1280                break;
1281    
1282              case JSON_PARSE_STATE_FINISHED:
1283                if (!JS_ISXMLSPACE(c)) {
1284                    // extra input
1285                    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
1286                    return JS_FALSE;
1287                }
1288                break;
1289    
1290              default:
1291                JS_NOT_REACHED("Invalid JSON parser state");
1292            }
1293    
1294            if (!STRING_BUFFER_OK(&jp->buffer)) {
1295                JS_ReportOutOfMemory(cx);
1296                return JS_FALSE;
1297            }
1298      }      }
1299    
1300        *jp->buffer.ptr = 0;
1301      return JS_TRUE;      return JS_TRUE;
1302  }  }
1303    
# Line 930  Line 1314 
1314  #if JS_HAS_TOSOURCE  #if JS_HAS_TOSOURCE
1315      JS_FN(js_toSource_str,  json_toSource,      0, 0),      JS_FN(js_toSource_str,  json_toSource,      0, 0),
1316  #endif  #endif
1317      JS_FN("parse",          js_json_parse,      0, 0),      JS_FN("parse",          js_json_parse,      1, 0),
1318      JS_FN("stringify",      js_json_stringify,  0, 0),      JS_FN("stringify",      js_json_stringify,  1, 0),
1319      JS_FS_END      JS_FS_END
1320  };  };
1321    

Legend:
Removed from v.399  
changed lines
  Added in v.460

  ViewVC Help
Powered by ViewVC 1.1.24