Bug 794323 - Remove the legacy JSON parsing mode, now that Firefox session store code doesn't need it. r=luke for the JS bits, r=jlebar for the DOM bits, r=mak77 for the browser/toolkit bits

--HG--
extra : rebase_source : b7a422eb52fe8e435143dfcc435d56e4886859e6
This commit is contained in:
Jeff Walden 2013-05-23 15:28:31 -07:00
parent 2286d2480d
commit bb39a16a0e
14 changed files with 80 additions and 312 deletions

View File

@ -1 +1 @@
{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]},]}
{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]}]}

View File

@ -13,9 +13,9 @@ interface nsIScriptGlobalObject;
[ptr] native JSContext(JSContext);
/**
* Encode and decode JSON text.
* Don't use this! Use JSON.parse and JSON.stringify directly.
*/
[scriptable, uuid(43845d58-1054-47fb-8be3-970b3f7bd7ea)]
[scriptable, uuid(083aebb0-7790-43b2-ae81-9e404e626236)]
interface nsIJSON : nsISupports
{
/**
@ -54,29 +54,4 @@ interface nsIJSON : nsISupports
// Make sure you GCroot the result of this function before using it.
[noscript] jsval decodeToJSVal(in AString str, in JSContext cx);
/*
* Decode a JSON string, but also accept some strings in non-JSON format, as
* the decoding methods here did previously before tightening.
*
* This method is provided only as a temporary transition path for users of
* the old code who depended on the ability to decode leniently; new users
* should use JSON.parse.
*
* This method must only be called from script.
*
* @param str the string to parse
*/
[implicit_jscontext]
jsval legacyDecode(in AString str);
/* Identical to legacyDecode, but decode the contents of stream. */
[implicit_jscontext]
jsval legacyDecodeFromStream(in nsIInputStream stream,
in long contentLength);
/* Identical to legacyDecode, but decode into a jsval. */
// Make sure you GCroot the result of this function before using it.
[noscript] jsval legacyDecodeToJSVal(in AString str, in JSContext cx);
};

View File

@ -401,8 +401,7 @@ nsJSON::DecodeInternal(JSContext* cx,
nsIInputStream *aStream,
int32_t aContentLength,
bool aNeedsConverter,
JS::Value* aRetval,
DecodingMode mode /* = STRICT */)
JS::Value* aRetval)
{
// Consume the stream
nsCOMPtr<nsIChannel> jsonChannel;
@ -419,7 +418,7 @@ nsJSON::DecodeInternal(JSContext* cx,
return NS_ERROR_FAILURE;
nsRefPtr<nsJSONListener> jsonListener =
new nsJSONListener(cx, aRetval, aNeedsConverter, mode);
new nsJSONListener(cx, aRetval, aNeedsConverter);
//XXX this stream pattern should be consolidated in netwerk
rv = jsonListener->OnStartRequest(jsonChannel, nullptr);
@ -468,44 +467,6 @@ nsJSON::DecodeInternal(JSContext* cx,
return NS_OK;
}
NS_IMETHODIMP
nsJSON::LegacyDecode(const nsAString& json, JSContext* cx, JS::Value* aRetval)
{
const PRUnichar *data;
uint32_t len = NS_StringGetData(json, &data);
nsCOMPtr<nsIInputStream> stream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
(const char*) data,
len * sizeof(PRUnichar),
NS_ASSIGNMENT_DEPEND);
NS_ENSURE_SUCCESS(rv, rv);
return DecodeInternal(cx, stream, len, false, aRetval, LEGACY);
}
NS_IMETHODIMP
nsJSON::LegacyDecodeFromStream(nsIInputStream *aStream, int32_t aContentLength,
JSContext* cx, JS::Value* aRetval)
{
return DecodeInternal(cx, aStream, aContentLength, true, aRetval, LEGACY);
}
NS_IMETHODIMP
nsJSON::LegacyDecodeToJSVal(const nsAString &str, JSContext *cx, JS::Value *result)
{
JS::RootedValue reviver(cx, JS::NullValue()), value(cx);
JS::StableCharPtr chars(static_cast<const jschar*>(PromiseFlatString(str).get()),
str.Length());
if (!js::ParseJSONWithReviver(cx, chars, str.Length(), reviver,
&value, LEGACY)) {
return NS_ERROR_UNEXPECTED;
}
*result = value;
return NS_OK;
}
nsresult
NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
{
@ -520,12 +481,10 @@ NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
}
nsJSONListener::nsJSONListener(JSContext *cx, JS::Value *rootVal,
bool needsConverter,
DecodingMode mode /* = STRICT */)
bool needsConverter)
: mNeedsConverter(needsConverter),
mCx(cx),
mRootVal(rootVal),
mDecodingMode(mode)
mRootVal(rootVal)
{
}
@ -568,10 +527,9 @@ nsJSONListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
JS::StableCharPtr chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
mBufferedChars.Length());
JSBool ok = js::ParseJSONWithReviver(mCx, chars,
(uint32_t) mBufferedChars.Length(),
reviver, &value,
mDecodingMode);
JSBool ok = JS_ParseJSONWithReviver(mCx, chars.get(),
uint32_t(mBufferedChars.Length()),
reviver, value.address());
*mRootVal = value;
mBufferedChars.TruncateLength(0);

View File

@ -7,7 +7,6 @@
#define nsJSON_h__
#include "jsapi.h"
#include "json.h"
#include "nsIJSON.h"
#include "nsString.h"
#include "nsCOMPtr.h"
@ -61,8 +60,7 @@ protected:
nsIInputStream* aStream,
int32_t aContentLength,
bool aNeedsConverter,
JS::Value* aRetVal,
DecodingMode mode = STRICT);
JS::Value* aRetVal);
nsCOMPtr<nsIURI> mURI;
};
@ -72,8 +70,7 @@ NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult);
class nsJSONListener : public nsIStreamListener
{
public:
nsJSONListener(JSContext *cx, JS::Value *rootVal, bool needsConverter,
DecodingMode mode);
nsJSONListener(JSContext *cx, JS::Value *rootVal, bool needsConverter);
virtual ~nsJSONListener();
NS_DECL_ISUPPORTS
@ -87,7 +84,6 @@ protected:
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
nsCString mSniffBuffer;
nsTArray<PRUnichar> mBufferedChars;
DecodingMode mDecodingMode;
nsresult ProcessBytes(const char* aBuffer, uint32_t aByteLength);
nsresult ConsumeConverted(const char* aBuffer, uint32_t aByteLength);
nsresult Consume(const PRUnichar *data, uint32_t len);

View File

@ -177,7 +177,7 @@ TryEvalJSON(JSContext *cx, JSScript *callerScript,
if (cp == end) {
bool isArray = (chars[0] == '[');
JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2,
JSONParser::StrictJSON, JSONParser::NoError);
JSONParser::NoError);
RootedValue tmp(cx);
if (!parser.parse(&tmp))
return EvalJSON_Failure;

View File

@ -1,7 +0,0 @@
// |jit-test| error: InternalError
function rec(x, self) {
if (a = parseLegacyJSON("[1 , ]").length)
self(x - 001 , self);
self(NaN, self);
}
rec(1, rec);

View File

@ -46,58 +46,6 @@ Class js::JSONClass = {
JS_ConvertStub
};
/* ES5 15.12.2. */
JSBool
js_json_parse(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
JSString *str = (argc >= 1) ? ToString<CanGC>(cx, args[0]) : cx->names().undefined;
if (!str)
return false;
JSStableString *stable = str->ensureStable(cx);
if (!stable)
return false;
JS::Anchor<JSString *> anchor(stable);
RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
/* Steps 2-5. */
return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
}
/* ES5 15.12.3. */
JSBool
js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
{
RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
? &vp[3].toObject()
: NULL);
RootedValue value(cx, (argc >= 1) ? vp[2] : UndefinedValue());
RootedValue space(cx, (argc >= 3) ? vp[4] : UndefinedValue());
StringBuffer sb(cx);
if (!js_Stringify(cx, &value, replacer, space, sb))
return false;
// XXX This can never happen to nsJSON.cpp, but the JSON object
// needs to support returning undefined. So this is a little awkward
// for the API, because we want to support streaming writers.
if (!sb.empty()) {
JSString *str = sb.finishString();
if (!str)
return false;
vp->setString(str);
} else {
vp->setUndefined();
}
return true;
}
static inline bool IsQuoteSpecialCharacter(jschar c)
{
JS_STATIC_ASSERT('\b' < ' ');
@ -855,13 +803,12 @@ Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp)
return Walk(cx, obj, id, reviver, vp);
}
JSBool
bool
js::ParseJSONWithReviver(JSContext *cx, StableCharPtr chars, size_t length, HandleValue reviver,
MutableHandleValue vp, DecodingMode decodingMode /* = STRICT */)
MutableHandleValue vp)
{
/* 15.12.2 steps 2-3. */
JSONParser parser(cx, chars, length,
decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
JSONParser parser(cx, chars, length);
if (!parser.parse(vp))
return false;
@ -880,6 +827,58 @@ json_toSource(JSContext *cx, unsigned argc, Value *vp)
}
#endif
/* ES5 15.12.2. */
JSBool
js_json_parse(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
JSString *str = (argc >= 1) ? ToString<CanGC>(cx, args[0]) : cx->names().undefined;
if (!str)
return false;
JSStableString *stable = str->ensureStable(cx);
if (!stable)
return false;
JS::Anchor<JSString *> anchor(stable);
RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
/* Steps 2-5. */
return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
}
/* ES5 15.12.3. */
JSBool
js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
{
RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
? &vp[3].toObject()
: NULL);
RootedValue value(cx, (argc >= 1) ? vp[2] : UndefinedValue());
RootedValue space(cx, (argc >= 3) ? vp[4] : UndefinedValue());
StringBuffer sb(cx);
if (!js_Stringify(cx, &value, replacer, space, sb))
return false;
// XXX This can never happen to nsJSON.cpp, but the JSON object
// needs to support returning undefined. So this is a little awkward
// for the API, because we want to support streaming writers.
if (!sb.empty()) {
JSString *str = sb.finishString();
if (!str)
return false;
vp->setString(str);
} else {
vp->setUndefined();
}
return true;
}
static const JSFunctionSpec json_static_methods[] = {
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, json_toSource, 0, 0),

View File

@ -7,42 +7,25 @@
#ifndef json_h___
#define json_h___
#include "jsprvtd.h"
#include "jspubtd.h"
#include "jsapi.h"
#include "js/CharacterEncoding.h"
#include "js/RootingAPI.h"
#include "js/Value.h"
#include "js/Vector.h"
#define JSON_MAX_DEPTH 2048
#define JSON_PARSER_BUFSIZE 1024
#include "vm/StringBuffer.h"
extern JSObject *
js_InitJSONClass(JSContext *cx, js::HandleObject obj);
extern JSBool
js_Stringify(JSContext *cx, js::MutableHandleValue vp,
JSObject *replacer, js::Value space,
js::StringBuffer &sb);
// Avoid build errors on certain platforms that define these names as constants
#undef STRICT
#undef LEGACY
/*
* The type of JSON decoding to perform. Strict decoding is to-the-spec;
* legacy decoding accepts a few non-JSON syntaxes historically accepted by the
* implementation. (Full description of these deviations is deliberately
* omitted.) New users should use strict decoding rather than legacy decoding,
* as legacy decoding might be removed at a future time.
*/
enum DecodingMode { STRICT, LEGACY };
js_Stringify(JSContext *cx, js::MutableHandleValue vp, JSObject *replacer,
js::Value space, js::StringBuffer &sb);
namespace js {
extern JS_FRIEND_API(JSBool)
ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue filter,
MutableHandleValue vp, DecodingMode decodingMode = STRICT);
extern bool
ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue reviver,
MutableHandleValue vp);
} /* namespace js */
} // namespace js
#endif /* json_h___ */

View File

@ -465,19 +465,6 @@ JSONParser::advancePropertyName()
if (*current == '"')
return readString<PropertyName>();
if (parsingMode == LegacyJSON && *current == '}') {
/*
* Previous JSON parsing accepted trailing commas in non-empty object
* syntax, and some users depend on this. (Specifically, Places data
* serialization in versions of Firefox before 4.0. We can remove this
* mode when profile upgrades from 3.6 become unsupported.) Permit
* such trailing commas only when legacy parsing is specifically
* requested.
*/
current++;
return token(ObjectClose);
}
error("expected double-quoted property name");
return token(Error);
}
@ -659,13 +646,6 @@ JSONParser::parse(MutableHandleValue vp)
}
goto JSONValue;
}
if (token == ObjectClose) {
JS_ASSERT(state == FinishObjectMember);
JS_ASSERT(parsingMode == LegacyJSON);
if (!finishObject(&value, stack.back().properties()))
return false;
break;
}
if (token == OOM)
return false;
if (token != Error)
@ -754,24 +734,6 @@ JSONParser::parse(MutableHandleValue vp)
}
case ArrayClose:
if (parsingMode == LegacyJSON &&
!stack.empty() &&
stack.back().state == FinishArrayElement) {
/*
* Previous JSON parsing accepted trailing commas in
* non-empty array syntax, and some users depend on this.
* (Specifically, Places data serialization in versions of
* Firefox prior to 4.0. We can remove this mode when
* profile upgrades from 3.6 become unsupported.) Permit
* such trailing commas only when specifically
* instructed to do so.
*/
if (!finishArray(&value, stack.back().elements()))
return false;
break;
}
/* FALL THROUGH */
case ObjectClose:
case Colon:
case Comma:

View File

@ -21,7 +21,6 @@ class JSONParser : private AutoGCRooter
{
public:
enum ErrorHandling { RaiseError, NoError };
enum ParsingMode { StrictJSON, LegacyJSON };
private:
/* Data members */
@ -32,7 +31,6 @@ class JSONParser : private AutoGCRooter
Value v;
const ParsingMode parsingMode;
const ErrorHandling errorHandling;
enum Token { String, Number, True, False, Null,
@ -113,20 +111,13 @@ class JSONParser : private AutoGCRooter
public:
/* Public API */
/*
* Create a parser for the provided JSON data. The parser will accept
* certain legacy, non-JSON syntax if decodingMode is LegacyJSON.
* Description of this syntax is deliberately omitted: new code should only
* use strict JSON parsing.
*/
/* Create a parser for the provided JSON data. */
JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
ParsingMode parsingMode = StrictJSON,
ErrorHandling errorHandling = RaiseError)
: AutoGCRooter(cx, JSONPARSER),
cx(cx),
current(data),
end((data + length).get(), data.get(), length),
parsingMode(parsingMode),
errorHandling(errorHandling),
stack(cx),
freeElements(cx),

View File

@ -41,7 +41,6 @@ EXPORTS += [
'jsdhash.h',
'jsfriendapi.h',
'jslock.h',
'json.h',
'jsperf.h',
'jsprf.h',
'jsprototypes.h',

View File

@ -3506,28 +3506,6 @@ NewGlobal(JSContext *cx, unsigned argc, jsval *vp)
return true;
}
static JSBool
ParseLegacyJSON(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (argc != 1 || !JSVAL_IS_STRING(args[0])) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "parseLegacyJSON");
return false;
}
JSString *str = JSVAL_TO_STRING(args[0]);
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return false;
RootedValue value(cx, NullValue());
return js::ParseJSONWithReviver(cx, StableCharPtr(chars, length), length,
value, args.rval(), LEGACY);
}
static JSBool
EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp)
{
@ -3895,11 +3873,6 @@ static JSFunctionSpecWithHelp shell_functions[] = {
" Return a new global object in a new compartment. If obj\n"
" is given, the compartment will be in the same zone as obj."),
JS_FN_HELP("parseLegacyJSON", ParseLegacyJSON, 1, 0,
"parseLegacyJSON(str)",
" Parse str as legacy JSON, returning the result if the\n"
" parse succeeded and throwing a SyntaxError if not."),
JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0,
"enableStackWalkingAssertion(enabled)",
" Enables or disables a particularly expensive assertion in stack-walking\n"

View File

@ -1,55 +0,0 @@
// |reftest| skip-if(!xulRuntime.shell) -- needs parseLegacyJSON
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
try
{
parseLegacyJSON("[,]");
throw new Error("didn't throw");
}
catch (e)
{
assertEq(e instanceof SyntaxError, true, "didn't get syntax error, got: " + e);
}
try
{
parseLegacyJSON("{,}");
throw new Error("didn't throw");
}
catch (e)
{
assertEq(e instanceof SyntaxError, true, "didn't get syntax error, got: " + e);
}
assertEq(parseLegacyJSON("[1,]").length, 1);
assertEq(parseLegacyJSON("[1, ]").length, 1);
assertEq(parseLegacyJSON("[1 , ]").length, 1);
assertEq(parseLegacyJSON("[1 ,]").length, 1);
assertEq(parseLegacyJSON("[1,2,]").length, 2);
assertEq(parseLegacyJSON("[1,2, ]").length, 2);
assertEq(parseLegacyJSON("[1,2 , ]").length, 2);
assertEq(parseLegacyJSON("[1,2 ,]").length, 2);
assertEq(parseLegacyJSON('{"a": 2,}').a, 2);
assertEq(parseLegacyJSON('{"a": 2, }').a, 2);
assertEq(parseLegacyJSON('{"a": 2 , }').a, 2);
assertEq(parseLegacyJSON('{"a": 2 ,}').a, 2);
var obj;
obj = parseLegacyJSON('{"a": 2,"b": 3,}');
assertEq(obj.a + obj.b, 5);
obj = parseLegacyJSON('{"a": 2,"b": 3, }');
assertEq(obj.a + obj.b, 5);
obj = parseLegacyJSON('{"a": 2,"b": 3 , }');
assertEq(obj.a + obj.b, 5);
obj = parseLegacyJSON('{"a": 2,"b": 3 ,}');
assertEq(obj.a + obj.b, 5);
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@ -688,13 +688,7 @@ this.PlacesUtils = {
case this.TYPE_X_MOZ_PLACE:
case this.TYPE_X_MOZ_PLACE_SEPARATOR:
case this.TYPE_X_MOZ_PLACE_CONTAINER:
var json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
// Old profiles (pre-Firefox 4) may contain bookmarks.json files with
// trailing commas, which we once accepted but no longer do -- except
// when decoded using the legacy decoder. This can be reverted to
// json.decode (better yet, to the ECMA-standard JSON.parse) when we no
// longer support upgrades from pre-Firefox 4 profiles.
nodes = json.legacyDecode("[" + blob + "]");
nodes = JSON.parse("[" + blob + "]");
break;
case this.TYPE_X_MOZ_URL:
var parts = blob.split("\n");