Bug 476374 - JSON.parse does not support reviver argument as defined in spec. r=jorendorff
authorRobert Sayre <sayrer@gmail.com>
Tue, 03 Mar 2009 12:55:11 -0500
changeset 25718 0bec239f232ec3a53412ba6030be4baf408a884f
parent 25717 f81ca529a6dcacd42029f46e12999ca6d4a44d60
child 25719 e31c4f602541b9380abc29e213fc276588d75917
child 25879 122cf18c1b7d78f21a76e7bf1f4eab5aa158096d
push id5699
push userrsayre@mozilla.com
push dateTue, 03 Mar 2009 18:53:04 +0000
treeherdermozilla-central@e31c4f602541 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs476374
milestone1.9.2a1pre
Bug 476374 - JSON.parse does not support reviver argument as defined in spec. r=jorendorff
dom/src/json/nsJSON.cpp
dom/src/json/test/unit/test_encoding_errors.js
dom/src/json/test/unit/test_reviver.js
dom/src/threads/nsDOMWorkerEvents.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfun.cpp
js/src/jsobj.h
js/src/json.cpp
js/src/json.h
js/src/jstracer.cpp
js/src/liveconnect/jsj_JavaObject.c
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -524,17 +524,17 @@ nsJSONListener::OnStopRequest(nsIRequest
   nsresult rv;
 
   // This can happen with short UTF-8 messages
   if (!mSniffBuffer.IsEmpty()) {
     rv = ProcessBytes(mSniffBuffer.get(), mSniffBuffer.Length());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  JSBool ok = JS_FinishJSONParse(mCx, mJSONParser);
+  JSBool ok = JS_FinishJSONParse(mCx, mJSONParser, JSVAL_NULL);
   mJSONParser = nsnull;
 
   if (!ok)
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
@@ -646,17 +646,17 @@ nsJSONListener::ConsumeConverted(const c
   rv = Consume(ustr.get(), unicharLength);
 
   return rv;
 }
 
 void nsJSONListener::Cleanup()
 {
   if (mJSONParser)
-    JS_FinishJSONParse(mCx, mJSONParser);
+    JS_FinishJSONParse(mCx, mJSONParser, JSVAL_NULL);
   mJSONParser = nsnull;
 }
 
 nsresult
 nsJSONListener::Consume(const PRUnichar* aBuffer, PRUint32 aByteLength)
 {
   if (!mJSONParser)
     return NS_ERROR_FAILURE;
--- a/dom/src/json/test/unit/test_encoding_errors.js
+++ b/dom/src/json/test/unit/test_encoding_errors.js
@@ -1,13 +1,13 @@
 function tooDeep() {
   var arr = [];
   var root = [arr];
   var tail;
-  for (var i = 0; i < 100000; i++) {
+  for (var i = 0; i < 5000; i++) {
     tail = [];
     arr.push(tail);
     arr = tail;
   }
   JSON.stringify(root);
 }
 function run_test() {
   do_check_eq("undefined", JSON.stringify(undefined));
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/unit/test_reviver.js
@@ -0,0 +1,21 @@
+function doubler(k, v) {
+  do_check_true("string" == typeof k);
+
+  if ((typeof v) == "number")
+    return 2 * v;
+
+  return v;
+}
+
+function run_test() {
+  var x = JSON.parse('{"a":5,"b":6}', doubler);
+  do_check_true(x.hasOwnProperty('a'));
+  do_check_true(x.hasOwnProperty('b'));
+  do_check_eq(x.a, 10);
+  do_check_eq(x.b, 12);
+
+  x = JSON.parse('[3, 4, 5]', doubler);
+  do_check_eq(x[0], 6);
+  do_check_eq(x[1], 8);
+  do_check_eq(x[2], 10);
+}
\ No newline at end of file
--- a/dom/src/threads/nsDOMWorkerEvents.cpp
+++ b/dom/src/threads/nsDOMWorkerEvents.cpp
@@ -302,17 +302,17 @@ nsDOMWorkerMessageEvent::GetData(nsAStri
 
   // This is slightly sneaky, but now that JS_BeginJSONParse succeeded we always
   // need call JS_FinishJSONParse even if JS_ConsumeJSONText fails. We'll report
   // an error if either failed, though.
   ok = JS_ConsumeJSONText(cx, parser, (jschar*)mData.get(),
                           (uint32)mData.Length());
 
   // Note the '&& ok' after the call here!
-  ok = JS_FinishJSONParse(cx, parser) && ok;
+  ok = JS_FinishJSONParse(cx, parser, JSVAL_NULL) && ok;
   if (!ok) {
     mCachedJSVal = JSVAL_NULL;
     return NS_ERROR_UNEXPECTED;
   }
 
   NS_ASSERTION(mCachedJSVal.ToJSObject(), "Bad JSON result!");
 
   if (mIsPrimitive) {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5683,20 +5683,20 @@ JS_BeginJSONParse(JSContext *cx, jsval *
 JS_PUBLIC_API(JSBool)
 JS_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
 {
     CHECK_REQUEST(cx);
     return js_ConsumeJSONText(cx, jp, data, len);
 }
 
 JS_PUBLIC_API(JSBool)
-JS_FinishJSONParse(JSContext *cx, JSONParser *jp)
+JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
 {
     CHECK_REQUEST(cx);
-    return js_FinishJSONParse(cx, jp);
+    return js_FinishJSONParse(cx, jp, reviver);
 }
 
 /*
  * The following determines whether C Strings are to be treated as UTF-8
  * or ISO-8859-1.  For correct operation, it must be set prior to the
  * first call to JS_NewRuntime.
  */
 #ifndef JS_C_STRINGS_ARE_UTF8
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2508,17 +2508,17 @@ JS_TryJSON(JSContext *cx, jsval *vp);
  */
 JS_PUBLIC_API(JSONParser *)
 JS_BeginJSONParse(JSContext *cx, jsval *vp);
 
 JS_PUBLIC_API(JSBool)
 JS_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len);
 
 JS_PUBLIC_API(JSBool)
-JS_FinishJSONParse(JSContext *cx, JSONParser *jp);
+JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver);
 
 /************************************************************************/
 
 /*
  * Locale specific string conversion and error message callbacks.
  */
 struct JSLocaleCallbacks {
     JSLocaleToUpperCase     localeToUpperCase;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2230,27 +2230,24 @@ js_ValueToFunctionObject(JSContext *cx, 
         return NULL;
     }
     return FUN_OBJECT(fun);
 }
 
 JSObject *
 js_ValueToCallableObject(JSContext *cx, jsval *vp, uintN flags)
 {
-    JSObject *callable;
+    JSObject *callable = JSVAL_IS_PRIMITIVE(*vp) ? NULL : JSVAL_TO_OBJECT(*vp);
 
-    callable = JSVAL_IS_PRIMITIVE(*vp) ? NULL : JSVAL_TO_OBJECT(*vp);
-    if (callable &&
-        ((callable->map->ops == &js_ObjectOps)
-         ? OBJ_GET_CLASS(cx, callable)->call
-         : callable->map->ops->call)) {
+    if (js_IsCallable(cx, callable)) {
         *vp = OBJECT_TO_JSVAL(callable);
     } else {
         callable = js_ValueToFunctionObject(cx, vp, flags);
     }
+
     return callable;
 }
 
 void
 js_ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags)
 {
     JSStackFrame *fp;
     uintN error;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -750,16 +750,24 @@ js_CheckPrincipalsAccess(JSContext *cx, 
 extern JSObject *
 js_GetWrappedObject(JSContext *cx, JSObject *obj);
 
 /* NB: Infallible. */
 extern const char *
 js_ComputeFilename(JSContext *cx, JSStackFrame *caller,
                    JSPrincipals *principals, uintN *linenop);
 
+/* TODO: bug 481218. This returns false for functions */
+static JS_INLINE JSBool
+js_IsCallable(JSContext *cx, JSObject *obj)
+{
+   return (obj && ((obj->map->ops == &js_ObjectOps) ? OBJ_GET_CLASS(cx, obj)->call
+                                                    : obj->map->ops->call));
+}
+
 #ifdef DEBUG
 JS_FRIEND_API(void) js_DumpChars(const jschar *s, size_t n);
 JS_FRIEND_API(void) js_DumpString(JSString *str);
 JS_FRIEND_API(void) js_DumpAtom(JSAtom *atom);
 JS_FRIEND_API(void) js_DumpValue(jsval val);
 JS_FRIEND_API(void) js_DumpId(jsid id);
 JS_FRIEND_API(void) js_DumpObject(JSObject *obj);
 #endif
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -63,25 +63,27 @@ JSClass js_JSONClass = {
     JSCLASS_NO_OPTIONAL_MEMBERS
 };
 
 JSBool
 js_json_parse(JSContext *cx, uintN argc, jsval *vp)
 {
     JSString *s = NULL;
     jsval *argv = vp + 2;
-
-    if (!JS_ConvertArguments(cx, argc, argv, "S", &s))
+    jsval reviver = JSVAL_VOID;
+    JSAutoTempValueRooter(cx, 1, &reviver);
+    
+    if (!JS_ConvertArguments(cx, argc, argv, "S / v", &s, &reviver))
         return JS_FALSE;
 
     JSONParser *jp = js_BeginJSONParse(cx, vp);
     JSBool ok = jp != NULL;
     if (ok) {
         ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));
-        ok &= js_FinishJSONParse(cx, jp);
+        ok &= js_FinishJSONParse(cx, jp, reviver);
     }
 
     return ok;
 }
 
 class StringifyClosure : JSAutoTempValueRooter
 {
 public:
@@ -274,17 +276,20 @@ stringify(JSContext *cx, jsval *vp, JSOb
     jsval key;
     JSBool memberWritten = JS_FALSE;
     do {
         outputValue = JSVAL_VOID;
 
         if (isArray) {
             if ((jsuint)i >= length)
                 break;
-            ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue);
+            jsid index;
+            if (!js_IndexToId(cx, i, &index))
+                return JS_FALSE;
+            ok = OBJ_GET_PROPERTY(cx, obj, index, &outputValue);
             if (!ok)
                 break;
             i++;
         } else {
             ok = js_CallIteratorNext(cx, iterObj, &key);
             if (!ok)
                 break;
             if (key == JSVAL_HOLE)
@@ -408,16 +413,117 @@ js_Stringify(JSContext *cx, jsval *vp, J
 static JSBool IsNumChar(jschar c)
 {
     return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
 }
 
 static JSBool HandleData(JSContext *cx, JSONParser *jp, JSONDataType type);
 static JSBool PopState(JSContext *cx, JSONParser *jp);
 
+static JSBool
+DestroyIdArrayOnError(JSContext *cx, JSIdArray *ida) {
+    JS_DestroyIdArray(cx, ida);
+    return JS_FALSE;
+}
+
+static JSBool
+Walk(JSContext *cx, jsid id, JSObject *holder, jsval reviver, jsval *vp)
+{
+    JS_CHECK_RECURSION(cx, return JS_FALSE);
+    
+    if (!OBJ_GET_PROPERTY(cx, holder, id, vp))
+        return JS_FALSE;
+
+    JSObject *obj;
+
+    if (!JSVAL_IS_PRIMITIVE(*vp) && !JS_ObjectIsFunction(cx, obj = JSVAL_TO_OBJECT(*vp)) &&
+        !js_IsCallable(cx, obj)) {
+        jsval propValue = JSVAL_VOID;
+        JSAutoTempValueRooter tvr(cx, 1, &propValue);
+        
+        if(OBJ_IS_ARRAY(cx, obj)) {
+            jsuint length = 0;
+            if (!js_GetLengthProperty(cx, obj, &length))
+                return JS_FALSE;
+
+            for (jsuint i = 0; i < length; i++) {
+                jsid index;
+                if (!js_IndexToId(cx, i, &index))
+                    return JS_FALSE;
+
+                if (!Walk(cx, index, obj, reviver, &propValue))
+                    return JS_FALSE;
+
+                if (!OBJ_DEFINE_PROPERTY(cx, obj, index, propValue,
+                                         NULL, NULL, JSPROP_ENUMERATE, NULL)) {
+                    return JS_FALSE;
+                }
+            }
+        } else {
+            JSIdArray *ida = JS_Enumerate(cx, obj);
+            if (!ida)
+                return JS_FALSE;
+
+            JSAutoTempValueRooter idaroot(cx, JS_ARRAY_LENGTH(ida), (jsval*)ida);
+
+            for(jsint i = 0; i < ida->length; i++) {
+                jsid idName = ida->vector[i];
+                if (!Walk(cx, idName, obj, reviver, &propValue))
+                    return DestroyIdArrayOnError(cx, ida);
+                if (propValue == JSVAL_VOID) {
+                    if (!js_DeleteProperty(cx, obj, idName, &propValue))
+                        return DestroyIdArrayOnError(cx, ida);
+                } else {
+                    if (!OBJ_DEFINE_PROPERTY(cx, obj, idName, propValue,
+                                             NULL, NULL, JSPROP_ENUMERATE, NULL)) {
+                        return DestroyIdArrayOnError(cx, ida);
+                    }
+                }
+            }
+
+            JS_DestroyIdArray(cx, ida);
+        }
+    }
+
+    // return reviver.call(holder, key, value);
+    jsval value = *vp;
+    JSString *key = js_ValueToString(cx, ID_TO_VALUE(id));
+    if (!key)
+        return JS_FALSE;
+
+    jsval vec[2] = {STRING_TO_JSVAL(key), value};
+    jsval reviverResult;
+    if (!JS_CallFunctionValue(cx, holder, reviver, 2, vec, &reviverResult))
+        return JS_FALSE;
+
+    *vp = reviverResult;
+
+    return JS_TRUE;
+}
+
+static JSBool
+Revive(JSContext *cx, jsval reviver, jsval *vp)
+{
+    
+    JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
+    if (!obj)
+        return JS_FALSE;
+
+    jsval v = OBJECT_TO_JSVAL(obj);
+    JSAutoTempValueRooter tvr(cx, 1, &v);
+    if (!OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom),
+                             *vp, NULL, NULL, JSPROP_ENUMERATE, NULL)) {
+        return JS_FALSE;
+    }
+
+    return Walk(cx, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), obj, reviver, vp);
+}
+
+
+
 JSONParser *
 js_BeginJSONParse(JSContext *cx, jsval *rootVal)
 {
     if (!cx)
         return NULL;
 
     JSObject *arr = js_NewArrayObject(cx, 0, NULL);
     if (!arr)
@@ -451,17 +557,17 @@ js_BeginJSONParse(JSContext *cx, jsval *
     return jp;
 bad:
     JS_free(cx, jp->buffer);
     JS_free(cx, jp);
     return NULL;
 }
 
 JSBool
-js_FinishJSONParse(JSContext *cx, JSONParser *jp)
+js_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
 {
     if (!jp)
         return JS_TRUE;
 
     // Check for unprocessed primitives at the root. This doesn't happen for
     // strings because a closing quote triggers value processing.
     if ((jp->statep - jp->stateStack) == 1) {
         if (*jp->statep == JSON_PARSE_STATE_KEYWORD) {
@@ -486,16 +592,21 @@ js_FinishJSONParse(JSContext *cx, JSONPa
     if (!js_RemoveRoot(cx->runtime, &jp->objectStack))
         return JS_FALSE;
     JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
     JS_free(cx, jp);
 
     if (!ok)
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
 
+    if (reviver && !JSVAL_IS_PRIMITIVE(reviver) &&
+        (JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(reviver)) || js_IsCallable(cx, JSVAL_TO_OBJECT(reviver)))) {
+        ok = Revive(cx, reviver, jp->rootVal);
+    }
+
     return ok;
 }
 
 static JSBool
 PushState(JSContext *cx, JSONParser *jp, JSONParserState state)
 {
     if (*jp->statep == JSON_PARSE_STATE_FINISHED) {
         // extra input
@@ -534,17 +645,20 @@ PopState(JSContext *cx, JSONParser *jp)
 static JSBool
 PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
 {
     JSBool ok;
     if (OBJ_IS_ARRAY(cx, parent)) {
         jsuint len;
         ok = js_GetLengthProperty(cx, parent, &len);
         if (ok) {
-            ok = OBJ_DEFINE_PROPERTY(cx, parent, INT_TO_JSID(len), value,
+            jsid index;
+            if (!js_IndexToId(cx, len, &index))
+                return JS_FALSE;
+            ok = OBJ_DEFINE_PROPERTY(cx, parent, index, value,
                                      NULL, NULL, JSPROP_ENUMERATE, NULL);
         }
     } else {
         ok = JS_DefineUCProperty(cx, parent, jp->objectKey->base,
                                  STRING_BUFFER_OFFSET(jp->objectKey), value,
                                  NULL, NULL, JSPROP_ENUMERATE);
         js_FinishStringBuffer(jp->objectKey);
         js_InitStringBuffer(jp->objectKey);
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -95,13 +95,13 @@ struct JSONParser {
 
 extern JSONParser *
 js_BeginJSONParse(JSContext *cx, jsval *rootVal);
 
 extern JSBool
 js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len);
 
 extern JSBool
-js_FinishJSONParse(JSContext *cx, JSONParser *jp);
+js_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver);
 
 JS_END_EXTERN_C
 
 #endif /* json_h___ */
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -47,17 +47,18 @@
 #define alloca _alloca
 #endif
 #ifdef SOLARIS
 #include <alloca.h>
 #endif
 #include <limits.h>
 
 #include "nanojit/nanojit.h"
-#include "jsarray.h"            // higher-level library and API headers
+#include "jsapi.h"              // higher-level library and API headers
+#include "jsarray.h"
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsdbgapi.h"
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsinterp.h"
 #include "jsiter.h"
 #include "jsobj.h"
--- a/js/src/liveconnect/jsj_JavaObject.c
+++ b/js/src/liveconnect/jsj_JavaObject.c
@@ -52,16 +52,17 @@
  *
  * An instance of JavaObject is the JavaScript reflection of a Java object.
  *
  */
 
 #include <stdlib.h>
 #include <string.h>
 
+#include "jsapi.h"
 #include "jsobj.h"
 #include "jsj_private.h"      /* LiveConnect internals */
 #include "jsj_hash.h"         /* Hash table with Java object as key */
 
 #ifdef JSJ_THREADSAFE
 #include "prmon.h"
 #endif