зеркало из https://github.com/mozilla/pjs.git
Bug 589664 - Rewrite the JSON parser. r=njn, anticipating more review but getting it in-tree now for simplicity, even if more changes need to be made later
This commit is contained in:
Родитель
d315235a27
Коммит
100a8553ea
|
@ -157,6 +157,7 @@ CPPSRCS = \
|
|||
jsnum.cpp \
|
||||
jsobj.cpp \
|
||||
json.cpp \
|
||||
jsonparser.cpp \
|
||||
jsopcode.cpp \
|
||||
jsparse.cpp \
|
||||
jsproxy.cpp \
|
||||
|
@ -218,6 +219,7 @@ INSTALLED_HEADERS = \
|
|||
jsobj.h \
|
||||
jsobjinlines.h \
|
||||
json.h \
|
||||
jsonparser.h \
|
||||
jsopcode.tbl \
|
||||
jsopcode.h \
|
||||
jsopcodeinlines.h \
|
||||
|
|
|
@ -307,7 +307,7 @@ MSG_DEF(JSMSG_BAD_OBJECT_INIT, 224, 0, JSEXN_SYNTAXERR, "invalid object i
|
|||
MSG_DEF(JSMSG_CANT_SET_ARRAY_ATTRS, 225, 0, JSEXN_INTERNALERR, "can't set attributes on indexed array properties")
|
||||
MSG_DEF(JSMSG_EVAL_ARITY, 226, 0, JSEXN_TYPEERR, "eval accepts only one parameter")
|
||||
MSG_DEF(JSMSG_MISSING_FUN_ARG, 227, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
|
||||
MSG_DEF(JSMSG_JSON_BAD_PARSE, 228, 0, JSEXN_SYNTAXERR, "JSON.parse")
|
||||
MSG_DEF(JSMSG_JSON_BAD_PARSE, 228, 1, JSEXN_SYNTAXERR, "JSON.parse: {0}")
|
||||
MSG_DEF(JSMSG_JSON_BAD_STRINGIFY, 229, 0, JSEXN_ERR, "JSON.stringify")
|
||||
MSG_DEF(JSMSG_XDR_CLOSURE_WRAPPER, 230, 1, JSEXN_INTERNALERR, "can't XDR closure wrapper for function {0}")
|
||||
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 231, 0, JSEXN_TYPEERR, "value is not a non-null object")
|
||||
|
|
|
@ -3216,6 +3216,7 @@ class AutoVectorRooter : protected AutoGCRooter
|
|||
void infallibleAppend(const T &v) { vector.infallibleAppend(v); }
|
||||
|
||||
void popBack() { vector.popBack(); }
|
||||
T popCopy() { return vector.popCopy(); }
|
||||
|
||||
bool growBy(size_t inc) {
|
||||
size_t oldLength = vector.length();
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
#include "jslock.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsobj.h"
|
||||
#include "jsonparser.h"
|
||||
#include "jsopcode.h"
|
||||
#include "jsparse.h"
|
||||
#include "jsproxy.h"
|
||||
|
@ -1215,6 +1216,7 @@ EvalKernel(JSContext *cx, uintN argc, Value *vp, EvalType evalType, JSStackFrame
|
|||
* will be lost.
|
||||
*/
|
||||
if (length > 2 && chars[0] == '(' && chars[length - 1] == ')') {
|
||||
#if USE_OLD_AND_BUSTED_JSON_PARSER
|
||||
JSONParser *jp = js_BeginJSONParse(cx, vp, /* suppressErrors = */true);
|
||||
if (jp != NULL) {
|
||||
/* Run JSON-parser on string inside ( and ). */
|
||||
|
@ -1223,6 +1225,14 @@ EvalKernel(JSContext *cx, uintN argc, Value *vp, EvalType evalType, JSStackFrame
|
|||
if (ok)
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
JSONSourceParser parser(cx, chars + 1, length - 2, JSONSourceParser::StrictJSON,
|
||||
JSONSourceParser::NoError);
|
||||
if (!parser.parse(vp))
|
||||
return false;
|
||||
if (!vp->isUndefined())
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#include "jsiter.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsobj.h"
|
||||
#include "jsonparser.h"
|
||||
#include "jsprf.h"
|
||||
#include "jsscan.h"
|
||||
#include "jsstr.h"
|
||||
|
@ -115,25 +116,17 @@ js_json_parse(JSContext *cx, uintN argc, Value *vp)
|
|||
{
|
||||
JSString *s = NULL;
|
||||
Value *argv = vp + 2;
|
||||
AutoValueRooter reviver(cx);
|
||||
Value reviver = UndefinedValue();
|
||||
|
||||
if (!JS_ConvertArguments(cx, argc, Jsvalify(argv), "S / v", &s, reviver.addr()))
|
||||
if (!JS_ConvertArguments(cx, argc, Jsvalify(argv), "S / v", &s, &reviver))
|
||||
return JS_FALSE;
|
||||
|
||||
JSLinearString *linearStr = s->ensureLinear(cx);
|
||||
if (!linearStr)
|
||||
return JS_FALSE;
|
||||
JS::Anchor<JSString *> anchor(linearStr);
|
||||
|
||||
JSONParser *jp = js_BeginJSONParse(cx, vp);
|
||||
JSBool ok = jp != NULL;
|
||||
if (ok) {
|
||||
const jschar *chars = linearStr->chars();
|
||||
size_t length = linearStr->length();
|
||||
ok = js_ConsumeJSONText(cx, jp, chars, length);
|
||||
ok &= !!js_FinishJSONParse(cx, jp, reviver.value());
|
||||
}
|
||||
|
||||
return ok;
|
||||
return ParseJSONWithReviver(cx, linearStr->chars(), linearStr->length(), reviver, vp);
|
||||
}
|
||||
|
||||
JSBool
|
||||
|
@ -768,7 +761,7 @@ static JSBool
|
|||
JSONParseError(JSONParser *jp, JSContext *cx)
|
||||
{
|
||||
if (!jp->suppressErrors)
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE);
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE, "syntax error");
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
|
@ -851,7 +844,7 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const Value &reviver)
|
|||
ok = false;
|
||||
} else if (!ok) {
|
||||
JSONParseError(jp, cx);
|
||||
} else if (reviver.isObject() && reviver.toObject().isCallable()) {
|
||||
} else if (js_IsCallable(reviver)) {
|
||||
ok = Revive(cx, reviver, vp);
|
||||
}
|
||||
|
||||
|
@ -863,15 +856,27 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const Value &reviver)
|
|||
namespace js {
|
||||
|
||||
JSBool
|
||||
ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32 length, const Value &reviver,
|
||||
ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, const Value &reviver,
|
||||
Value *vp, DecodingMode decodingMode /* = STRICT */)
|
||||
{
|
||||
#if USE_OLD_AND_BUSTED_JSON_PARSER
|
||||
JSONParser *jp = js_BeginJSONParse(cx, vp);
|
||||
if (!jp)
|
||||
return false;
|
||||
JSBool ok = js_ConsumeJSONText(cx, jp, chars, length, decodingMode);
|
||||
ok &= !!js_FinishJSONParse(cx, jp, reviver);
|
||||
return ok;
|
||||
#else
|
||||
JSONSourceParser parser(cx, chars, length,
|
||||
decodingMode == STRICT
|
||||
? JSONSourceParser::StrictJSON
|
||||
: JSONSourceParser::LegacyJSON);
|
||||
if (!parser.parse(vp))
|
||||
return false;
|
||||
if (js_IsCallable(reviver))
|
||||
return Revive(cx, reviver, vp);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
} /* namespace js */
|
||||
|
|
|
@ -137,7 +137,7 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const js::Value &reviver);
|
|||
namespace js {
|
||||
|
||||
extern JS_FRIEND_API(JSBool)
|
||||
ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32 length, const Value &filter,
|
||||
ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, const Value &filter,
|
||||
Value *vp, DecodingMode decodingMode = STRICT);
|
||||
|
||||
} /* namespace js */
|
||||
|
|
|
@ -0,0 +1,695 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sw=4 et tw=99:
|
||||
*
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is SpiderMonkey JSON.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jeff Walden <jwalden+code@mit.edu> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "jsarray.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsonparser.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
#include "jsstrinlines.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
void
|
||||
JSONSourceParser::error(const char *msg)
|
||||
{
|
||||
if (errorHandling == RaiseError)
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE, msg);
|
||||
}
|
||||
|
||||
bool
|
||||
JSONSourceParser::errorReturn()
|
||||
{
|
||||
return errorHandling == NoError;
|
||||
}
|
||||
|
||||
template<JSONSourceParser::StringType ST>
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::readString()
|
||||
{
|
||||
JS_ASSERT(current < end);
|
||||
JS_ASSERT(*current == '"');
|
||||
|
||||
/*
|
||||
* JSONString:
|
||||
* /^"([^\u0000-\u001F"\\]|\\(["/\\bfnrt]|u[0-9a-fA-F]{4}))*"$/
|
||||
*/
|
||||
|
||||
if (++current == end) {
|
||||
error("unterminated string literal");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimization: if the source contains no escaped characters, create the
|
||||
* string directly from the source text.
|
||||
*/
|
||||
RangeCheckedPointer<const jschar> start = current;
|
||||
for (; current < end; current++) {
|
||||
if (*current == '"') {
|
||||
size_t length = current - start;
|
||||
current++;
|
||||
JSFlatString *str = (ST == JSONSourceParser::PropertyName)
|
||||
? js_AtomizeChars(cx, start, length, 0)
|
||||
: js_NewStringCopyN(cx, start, length);
|
||||
if (!str)
|
||||
return token(OOM);
|
||||
return stringToken(str);
|
||||
}
|
||||
|
||||
if (*current == '\\')
|
||||
break;
|
||||
|
||||
if (*current <= 0x001F) {
|
||||
error("bad control character in string literal");
|
||||
return token(Error);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Slow case: string contains escaped characters. Copy a maximal sequence
|
||||
* of unescaped characters into a temporary buffer, then an escaped
|
||||
* character, and repeat until the entire string is consumed.
|
||||
*/
|
||||
StringBuffer buffer(cx);
|
||||
do {
|
||||
if (start < current && !buffer.append(start, current))
|
||||
return token(OOM);
|
||||
|
||||
if (current >= end)
|
||||
break;
|
||||
|
||||
jschar c = *current++;
|
||||
if (c == '"') {
|
||||
JSFlatString *str = (ST == JSONSourceParser::PropertyName)
|
||||
? buffer.finishAtom()
|
||||
: buffer.finishString();
|
||||
if (!str)
|
||||
return token(OOM);
|
||||
return stringToken(str);
|
||||
}
|
||||
|
||||
if (c != '\\') {
|
||||
error("bad character in string literal");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
if (current >= end)
|
||||
break;
|
||||
|
||||
switch (*current++) {
|
||||
case '"': c = '"'; break;
|
||||
case '/': c = '/'; break;
|
||||
case '\\': c = '\\'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'f': c = '\f'; break;
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
|
||||
case 'u':
|
||||
if (end - current < 4) {
|
||||
error("bad Unicode escape");
|
||||
return token(Error);
|
||||
}
|
||||
if (JS7_ISHEX(current[0]) &&
|
||||
JS7_ISHEX(current[1]) &&
|
||||
JS7_ISHEX(current[2]) &&
|
||||
JS7_ISHEX(current[3]))
|
||||
{
|
||||
c = (JS7_UNHEX(current[0]) << 12)
|
||||
| (JS7_UNHEX(current[1]) << 8)
|
||||
| (JS7_UNHEX(current[2]) << 4)
|
||||
| (JS7_UNHEX(current[3]));
|
||||
current += 4;
|
||||
break;
|
||||
}
|
||||
/* FALL THROUGH */
|
||||
|
||||
default:
|
||||
error("bad escaped character");
|
||||
return token(Error);
|
||||
}
|
||||
if (!buffer.append(c))
|
||||
return token(OOM);
|
||||
|
||||
start = current;
|
||||
for (; current < end; current++) {
|
||||
if (*current == '"' || *current == '\\' || *current <= 0x001F)
|
||||
break;
|
||||
}
|
||||
} while (current < end);
|
||||
|
||||
error("unterminated string");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::readNumber()
|
||||
{
|
||||
JS_ASSERT(current < end);
|
||||
JS_ASSERT(JS7_ISDEC(*current) || *current == '-');
|
||||
|
||||
/*
|
||||
* JSONNumber:
|
||||
* /^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][\+\-]?[0-9]+)?$/
|
||||
*/
|
||||
|
||||
bool negative = *current == '-';
|
||||
|
||||
/* -? */
|
||||
if (negative && ++current == end) {
|
||||
error("no number after minus sign");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
const RangeCheckedPointer<const jschar> digitStart = current;
|
||||
|
||||
/* 0|[1-9][0-9]+ */
|
||||
if (!JS7_ISDEC(*current)) {
|
||||
error("unexpected non-digit");
|
||||
return token(Error);
|
||||
}
|
||||
if (*current++ != '0') {
|
||||
for (; current < end; current++) {
|
||||
if (!JS7_ISDEC(*current))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fast path: no fractional or exponent part. */
|
||||
if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) {
|
||||
const jschar *dummy;
|
||||
jsdouble d;
|
||||
if (!GetPrefixInteger(cx, digitStart, current, 10, &dummy, &d))
|
||||
return token(OOM);
|
||||
JS_ASSERT(current == dummy);
|
||||
return numberToken(negative ? -d : d);
|
||||
}
|
||||
|
||||
/* (\.[0-9]+)? */
|
||||
if (current < end && *current == '.') {
|
||||
if (++current == end) {
|
||||
error("missing digits after decimal point");
|
||||
return token(Error);
|
||||
}
|
||||
if (!JS7_ISDEC(*current)) {
|
||||
error("unterminated fractional number");
|
||||
return token(Error);
|
||||
}
|
||||
while (++current < end) {
|
||||
if (!JS7_ISDEC(*current))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ([eE][\+\-]?[0-9]+)? */
|
||||
if (current < end && (*current == 'e' || *current == 'E')) {
|
||||
if (++current == end) {
|
||||
error("missing digits after exponent indicator");
|
||||
return token(Error);
|
||||
}
|
||||
if (*current == '+' || *current == '-') {
|
||||
if (++current == end) {
|
||||
error("missing digits after exponent sign");
|
||||
return token(Error);
|
||||
}
|
||||
}
|
||||
if (!JS7_ISDEC(*current)) {
|
||||
error("exponent part is missing a number");
|
||||
return token(Error);
|
||||
}
|
||||
while (++current < end) {
|
||||
if (!JS7_ISDEC(*current))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jsdouble d;
|
||||
const jschar *finish;
|
||||
if (!js_strtod(cx, digitStart, current, &finish, &d))
|
||||
return token(OOM);
|
||||
JS_ASSERT(current == finish);
|
||||
return numberToken(negative ? -d : d);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsJSONWhitespace(jschar c)
|
||||
{
|
||||
return c == '\t' || c == '\r' || c == '\n' || c == ' ';
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advance()
|
||||
{
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("unexpected end of data");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
switch (*current) {
|
||||
case '"':
|
||||
return readString<LiteralValue>();
|
||||
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return readNumber();
|
||||
|
||||
case 't':
|
||||
if (end - current < 4 || current[1] != 'r' || current[2] != 'u' || current[3] != 'e') {
|
||||
error("unexpected keyword");
|
||||
return token(Error);
|
||||
}
|
||||
current += 4;
|
||||
return token(True);
|
||||
|
||||
case 'f':
|
||||
if (end - current < 5 ||
|
||||
current[1] != 'a' || current[2] != 'l' || current[3] != 's' || current[4] != 'e')
|
||||
{
|
||||
error("unexpected keyword");
|
||||
return token(Error);
|
||||
}
|
||||
current += 5;
|
||||
return token(False);
|
||||
|
||||
case 'n':
|
||||
if (end - current < 4 || current[1] != 'u' || current[2] != 'l' || current[3] != 'l') {
|
||||
error("unexpected keyword");
|
||||
return token(Error);
|
||||
}
|
||||
current += 4;
|
||||
return token(Null);
|
||||
|
||||
case '[':
|
||||
current++;
|
||||
return token(ArrayOpen);
|
||||
case ']':
|
||||
current++;
|
||||
return token(ArrayClose);
|
||||
|
||||
case '{':
|
||||
current++;
|
||||
return token(ObjectOpen);
|
||||
case '}':
|
||||
current++;
|
||||
return token(ObjectClose);
|
||||
|
||||
case ',':
|
||||
current++;
|
||||
return token(Comma);
|
||||
|
||||
case ':':
|
||||
current++;
|
||||
return token(Colon);
|
||||
|
||||
default:
|
||||
error("unexpected character");
|
||||
return token(Error);
|
||||
}
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advanceAfterObjectOpen()
|
||||
{
|
||||
JS_ASSERT(current[-1] == '{');
|
||||
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("end of data while reading object contents");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
if (*current == '"')
|
||||
return readString<PropertyName>();
|
||||
|
||||
if (*current == '}') {
|
||||
current++;
|
||||
return token(ObjectClose);
|
||||
}
|
||||
|
||||
error("expected property name or '}'");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
static inline void
|
||||
AssertPastValue(const jschar *current)
|
||||
{
|
||||
/*
|
||||
* We're past an arbitrary JSON value, so the previous character is
|
||||
* *somewhat* constrained, even if this assertion is pretty broad. Don't
|
||||
* knock it till you tried it: this assertion *did* catch a bug once.
|
||||
*/
|
||||
JS_ASSERT((current[-1] == 'l' &&
|
||||
current[-2] == 'l' &&
|
||||
current[-3] == 'u' &&
|
||||
current[-4] == 'n') ||
|
||||
(current[-1] == 'e' &&
|
||||
current[-2] == 'u' &&
|
||||
current[-3] == 'r' &&
|
||||
current[-4] == 't') ||
|
||||
(current[-1] == 'e' &&
|
||||
current[-2] == 's' &&
|
||||
current[-3] == 'l' &&
|
||||
current[-4] == 'a' &&
|
||||
current[-5] == 'f') ||
|
||||
current[-1] == '}' ||
|
||||
current[-1] == ']' ||
|
||||
current[-1] == '"' ||
|
||||
JS7_ISDEC(current[-1]));
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advanceAfterArrayElement()
|
||||
{
|
||||
AssertPastValue(current);
|
||||
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("end of data when ',' or ']' was expected");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
if (*current == ',') {
|
||||
current++;
|
||||
return token(Comma);
|
||||
}
|
||||
|
||||
if (*current == ']') {
|
||||
current++;
|
||||
return token(ArrayClose);
|
||||
}
|
||||
|
||||
error("expected ',' or ']' after array element");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advancePropertyName()
|
||||
{
|
||||
JS_ASSERT(current[-1] == ',');
|
||||
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("end of data when property name was expected");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advancePropertyColon()
|
||||
{
|
||||
JS_ASSERT(current[-1] == '"');
|
||||
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("end of data after property name when ':' was expected");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
if (*current == ':') {
|
||||
current++;
|
||||
return token(Colon);
|
||||
}
|
||||
|
||||
error("expected ':' after property name in object");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
JSONSourceParser::Token
|
||||
JSONSourceParser::advanceAfterProperty()
|
||||
{
|
||||
AssertPastValue(current);
|
||||
|
||||
while (current < end && IsJSONWhitespace(*current))
|
||||
current++;
|
||||
if (current >= end) {
|
||||
error("end of data after property value in object");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
if (*current == ',') {
|
||||
current++;
|
||||
return token(Comma);
|
||||
}
|
||||
|
||||
if (*current == '}') {
|
||||
current++;
|
||||
return token(ObjectClose);
|
||||
}
|
||||
|
||||
error("expected ',' or '}' after property value in object");
|
||||
return token(Error);
|
||||
}
|
||||
|
||||
/*
|
||||
* This enum is local to JSONSourceParser::parse, below, but ISO C++98 doesn't
|
||||
* allow templates to depend on local types. Boo-urns!
|
||||
*/
|
||||
enum ParserState { FinishArrayElement, FinishObjectMember, JSONValue };
|
||||
|
||||
bool
|
||||
JSONSourceParser::parse(Value *vp)
|
||||
{
|
||||
Vector<ParserState> stateStack(cx);
|
||||
AutoValueVector valueStack(cx);
|
||||
|
||||
*vp = UndefinedValue();
|
||||
|
||||
Token token;
|
||||
ParserState state = JSONValue;
|
||||
while (true) {
|
||||
switch (state) {
|
||||
case FinishObjectMember: {
|
||||
Value v = valueStack.popCopy();
|
||||
/*
|
||||
* NB: Relies on js_DefineNativeProperty performing
|
||||
* js_CheckForStringIndex.
|
||||
*/
|
||||
jsid propid = ATOM_TO_JSID(&valueStack.popCopy().toString()->asAtom());
|
||||
if (!js_DefineNativeProperty(cx, &valueStack.back().toObject(), propid, v,
|
||||
PropertyStub, StrictPropertyStub, JSPROP_ENUMERATE,
|
||||
0, 0, NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
token = advanceAfterProperty();
|
||||
if (token == ObjectClose)
|
||||
break;
|
||||
if (token != Comma) {
|
||||
if (token == OOM)
|
||||
return false;
|
||||
if (token != Error)
|
||||
error("expected ',' or '}' after property-value pair in object literal");
|
||||
return errorReturn();
|
||||
}
|
||||
token = advancePropertyName();
|
||||
/* FALL THROUGH */
|
||||
}
|
||||
|
||||
JSONMember:
|
||||
if (token == String) {
|
||||
if (!valueStack.append(atomValue()))
|
||||
return false;
|
||||
token = advancePropertyColon();
|
||||
if (token != Colon) {
|
||||
JS_ASSERT(token == Error);
|
||||
return errorReturn();
|
||||
}
|
||||
if (!stateStack.append(FinishObjectMember))
|
||||
return false;
|
||||
goto JSONValue;
|
||||
}
|
||||
if (token == ObjectClose) {
|
||||
JS_ASSERT(state == FinishObjectMember);
|
||||
JS_ASSERT(parsingMode == LegacyJSON);
|
||||
break;
|
||||
}
|
||||
if (token == OOM)
|
||||
return false;
|
||||
if (token != Error)
|
||||
error("property names must be double-quoted strings");
|
||||
return errorReturn();
|
||||
|
||||
case FinishArrayElement: {
|
||||
Value v = valueStack.popCopy();
|
||||
if (!js_ArrayCompPush(cx, &valueStack.back().toObject(), v))
|
||||
return false;
|
||||
token = advanceAfterArrayElement();
|
||||
if (token == Comma) {
|
||||
if (!stateStack.append(FinishArrayElement))
|
||||
return false;
|
||||
goto JSONValue;
|
||||
}
|
||||
if (token == ArrayClose)
|
||||
break;
|
||||
JS_ASSERT(token == Error);
|
||||
return errorReturn();
|
||||
}
|
||||
|
||||
JSONValue:
|
||||
case JSONValue:
|
||||
token = advance();
|
||||
JSONValueSwitch:
|
||||
switch (token) {
|
||||
case String:
|
||||
case Number:
|
||||
if (!valueStack.append(token == String ? stringValue() : numberValue()))
|
||||
return false;
|
||||
break;
|
||||
case True:
|
||||
if (!valueStack.append(BooleanValue(true)))
|
||||
return false;
|
||||
break;
|
||||
case False:
|
||||
if (!valueStack.append(BooleanValue(false)))
|
||||
return false;
|
||||
break;
|
||||
case Null:
|
||||
if (!valueStack.append(NullValue()))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case ArrayOpen: {
|
||||
JSObject *obj = NewDenseEmptyArray(cx);
|
||||
if (!obj || !valueStack.append(ObjectValue(*obj)))
|
||||
return false;
|
||||
token = advance();
|
||||
if (token == ArrayClose)
|
||||
break;
|
||||
if (!stateStack.append(FinishArrayElement))
|
||||
return false;
|
||||
goto JSONValueSwitch;
|
||||
}
|
||||
|
||||
case ObjectOpen: {
|
||||
JSObject *obj = NewBuiltinClassInstance(cx, &js_ObjectClass);
|
||||
if (!obj || !valueStack.append(ObjectValue(*obj)))
|
||||
return false;
|
||||
token = advanceAfterObjectOpen();
|
||||
if (token == ObjectClose)
|
||||
break;
|
||||
goto JSONMember;
|
||||
}
|
||||
|
||||
case ArrayClose:
|
||||
if (parsingMode == LegacyJSON &&
|
||||
!stateStack.empty() &&
|
||||
stateStack.back() == 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.
|
||||
*/
|
||||
stateStack.popBack();
|
||||
break;
|
||||
}
|
||||
/* FALL THROUGH */
|
||||
|
||||
case ObjectClose:
|
||||
case Colon:
|
||||
case Comma:
|
||||
error("unexpected character");
|
||||
return errorReturn();
|
||||
|
||||
case OOM:
|
||||
return false;
|
||||
|
||||
case Error:
|
||||
return errorReturn();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stateStack.empty())
|
||||
break;
|
||||
state = stateStack.popCopy();
|
||||
}
|
||||
|
||||
for (; current < end; current++) {
|
||||
if (!IsJSONWhitespace(*current)) {
|
||||
error("unexpected non-whitespace character after JSON data");
|
||||
return errorReturn();
|
||||
}
|
||||
}
|
||||
|
||||
JS_ASSERT(end == current);
|
||||
JS_ASSERT(valueStack.length() == 1);
|
||||
*vp = valueStack[0];
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sw=4 et tw=99:
|
||||
*
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is SpiderMonkey JSON.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jeff Walden <jwalden+code@mit.edu> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef jsonparser_h___
|
||||
#define jsonparser_h___
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jsstr.h"
|
||||
#include "jstl.h"
|
||||
#include "jsvalue.h"
|
||||
|
||||
/*
|
||||
* This class should be JSONParser, but the old JSON parser uses that name, so
|
||||
* until we remove the old parser the class name will be overlong.
|
||||
*
|
||||
* NB: This class must only be used on the stack as it contains a js::Value.
|
||||
*/
|
||||
class JSONSourceParser
|
||||
{
|
||||
public:
|
||||
enum ErrorHandling { RaiseError, NoError };
|
||||
enum ParsingMode { StrictJSON, LegacyJSON };
|
||||
|
||||
private:
|
||||
/* Data members */
|
||||
|
||||
JSContext * const cx;
|
||||
js::RangeCheckedPointer<const jschar> current;
|
||||
const js::RangeCheckedPointer<const jschar> end;
|
||||
|
||||
js::Value v;
|
||||
|
||||
const ParsingMode parsingMode;
|
||||
const ErrorHandling errorHandling;
|
||||
|
||||
enum Token { String, Number, True, False, Null,
|
||||
ArrayOpen, ArrayClose,
|
||||
ObjectOpen, ObjectClose,
|
||||
Colon, Comma,
|
||||
OOM, Error };
|
||||
#ifdef DEBUG
|
||||
Token lastToken;
|
||||
#endif
|
||||
|
||||
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.
|
||||
*/
|
||||
JSONSourceParser(JSContext *cx, const jschar *data, size_t length,
|
||||
ParsingMode parsingMode = StrictJSON,
|
||||
ErrorHandling errorHandling = RaiseError)
|
||||
: cx(cx),
|
||||
current(data, data, length),
|
||||
end(data + length, data, length),
|
||||
parsingMode(parsingMode),
|
||||
errorHandling(errorHandling)
|
||||
#ifdef DEBUG
|
||||
, lastToken(Error)
|
||||
#endif
|
||||
{
|
||||
JS_ASSERT(current <= end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the JSON data specified at construction time. If it parses
|
||||
* successfully, store the prescribed value in *vp and return true. If an
|
||||
* internal error (e.g. OOM) occurs during parsing, return false.
|
||||
* Otherwise, if invalid input was specifed but no internal error occurred,
|
||||
* behavior depends upon the error handling specified at construction: if
|
||||
* error handling is RaiseError then throw a SyntaxError and return false,
|
||||
* otherwise return true and set *vp to |undefined|. (JSON syntax can't
|
||||
* represent |undefined|, so the JSON data couldn't have specified it.)
|
||||
*/
|
||||
bool parse(js::Value *vp);
|
||||
|
||||
private:
|
||||
js::Value numberValue() const {
|
||||
JS_ASSERT(lastToken == Number);
|
||||
JS_ASSERT(v.isNumber());
|
||||
return v;
|
||||
}
|
||||
|
||||
js::Value stringValue() const {
|
||||
JS_ASSERT(lastToken == String);
|
||||
JS_ASSERT(v.isString());
|
||||
return v;
|
||||
}
|
||||
|
||||
js::Value atomValue() const {
|
||||
js::Value strval = stringValue();
|
||||
JS_ASSERT(strval.toString()->isAtom());
|
||||
return strval;
|
||||
}
|
||||
|
||||
Token token(Token t) {
|
||||
JS_ASSERT(t != String);
|
||||
JS_ASSERT(t != Number);
|
||||
#ifdef DEBUG
|
||||
lastToken = t;
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
Token stringToken(JSString *str) {
|
||||
this->v = js::StringValue(str);
|
||||
#ifdef DEBUG
|
||||
lastToken = String;
|
||||
#endif
|
||||
return String;
|
||||
}
|
||||
|
||||
Token numberToken(jsdouble d) {
|
||||
this->v = js::NumberValue(d);
|
||||
#ifdef DEBUG
|
||||
lastToken = Number;
|
||||
#endif
|
||||
return Number;
|
||||
}
|
||||
|
||||
enum StringType { PropertyName, LiteralValue };
|
||||
template<StringType ST> Token readString();
|
||||
|
||||
Token readNumber();
|
||||
|
||||
Token advance();
|
||||
Token advancePropertyName();
|
||||
Token advancePropertyColon();
|
||||
Token advanceAfterProperty();
|
||||
Token advanceAfterObjectOpen();
|
||||
Token advanceAfterArrayElement();
|
||||
|
||||
void error(const char *msg);
|
||||
bool errorReturn();
|
||||
};
|
||||
|
||||
#endif /* jsonparser_h___ */
|
|
@ -219,3 +219,13 @@
|
|||
* support likely to be made opt-in at some future time.
|
||||
*/
|
||||
#define OLD_GETTER_SETTER_METHODS 1
|
||||
|
||||
/*
|
||||
* Embedders: don't change this: it's a bake-until-ready hack only!
|
||||
*
|
||||
* NB: Changing this value requires adjusting the pass/fail state of a handful
|
||||
* of tests in ecma_5/JSON/ which the old parser implemented incorrectly.
|
||||
* Also make sure to rename JSONSourceParser to just JSONParser when the
|
||||
* old parser is removed completely.
|
||||
*/
|
||||
#define USE_OLD_AND_BUSTED_JSON_PARSER 0
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
#include "jslock.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsobj.h"
|
||||
#include "json.h"
|
||||
#include "jsparse.h"
|
||||
#include "jsreflect.h"
|
||||
#include "jsscope.h"
|
||||
|
@ -4595,6 +4596,23 @@ NewGlobal(JSContext *cx, uintN argc, jsval *vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
ParseLegacyJSON(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
if (argc != 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) {
|
||||
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "parseLegacyJSON");
|
||||
return false;
|
||||
}
|
||||
|
||||
JSString *str = JSVAL_TO_STRING(JS_ARGV(cx, vp)[0]);
|
||||
|
||||
size_t length;
|
||||
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
|
||||
if (!chars)
|
||||
return false;
|
||||
return js::ParseJSONWithReviver(cx, chars, length, js::NullValue(), js::Valueify(vp), LEGACY);
|
||||
}
|
||||
|
||||
static JSFunctionSpec shell_functions[] = {
|
||||
JS_FN("version", Version, 0,0),
|
||||
JS_FN("revertVersion", RevertVersion, 0,0),
|
||||
|
@ -4694,6 +4712,7 @@ static JSFunctionSpec shell_functions[] = {
|
|||
#endif
|
||||
JS_FN("stringstats", StringStats, 0,0),
|
||||
JS_FN("newGlobal", NewGlobal, 1,0),
|
||||
JS_FN("parseLegacyJSON",ParseLegacyJSON,1,0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
@ -4829,6 +4848,8 @@ static const char *const shell_help_messages[] = {
|
|||
"newGlobal(kind) Return a new global object, in the current\n"
|
||||
" compartment if kind === 'same-compartment' or in a\n"
|
||||
" new compartment if kind === 'new-compartment'",
|
||||
"parseLegacyJSON(str) Parse str as legacy JSON, returning the result if the\n"
|
||||
" parse succeeded and throwing a SyntaxError if not.",
|
||||
|
||||
/* Keep these last: see the static assertion below. */
|
||||
#ifdef MOZ_PROFILING
|
||||
|
|
|
@ -5,7 +5,6 @@ script parse.js
|
|||
script parse-crockford-01.js
|
||||
script parse-primitives.js
|
||||
script parse-reviver.js
|
||||
fails script parse-octal-syntax-error.js # our JSON.parse wrongly accepts octal numbers
|
||||
script parse-syntax-errors-01.js
|
||||
script parse-syntax-errors-02.js
|
||||
script stringify.js
|
||||
|
@ -20,3 +19,11 @@ script stringify-replacer.js
|
|||
script stringify-replacer-with-array-indexes.js
|
||||
script stringify-toJSON-arguments.js
|
||||
script trailing-comma.js
|
||||
|
||||
# These tests pass with the new parser but fail with the old one. If you're
|
||||
# changing which parser is used, you'll need to change these test declarations
|
||||
# accordingly. (And if you're removing the old parser, re-alphabetize these
|
||||
# tests into the list above.)
|
||||
script parse-number-syntax.js
|
||||
script parse-octal-syntax-error.js
|
||||
script parse-syntax-errors-03.js
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
testJSON('-', true);
|
||||
testJSON('+', true);
|
||||
testJSON('-f', true);
|
||||
testJSON('+f', true);
|
||||
testJSON('00', true);
|
||||
testJSON('01', true);
|
||||
testJSON('1.', true);
|
||||
testJSON('1.0e', true);
|
||||
testJSON('1.0e+', true);
|
||||
testJSON('1.0e-', true);
|
||||
testJSON('1.0e+z', true);
|
||||
testJSON('1.0e-z', true);
|
||||
testJSON('1.0ee', true);
|
||||
testJSON('1.e1', true);
|
||||
testJSON('1.e+1', true);
|
||||
testJSON('1.e-1', true);
|
||||
testJSON('.', true);
|
||||
testJSON('.1', true);
|
||||
testJSON('.1e', true);
|
||||
testJSON('.1e1', true);
|
||||
testJSON('.1e+1', true);
|
||||
testJSON('.1e-1', true);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
|
@ -0,0 +1,55 @@
|
|||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
testJSON('[', true);
|
||||
testJSON('[1', true);
|
||||
testJSON('[1,]', true);
|
||||
testJSON('[1,{', true);
|
||||
testJSON('[1,}', true);
|
||||
testJSON('[1,{]', true);
|
||||
testJSON('[1,}]', true);
|
||||
testJSON('[1,{"', true);
|
||||
testJSON('[1,}"', true);
|
||||
testJSON('[1,{"\\', true);
|
||||
testJSON('[1,}"\\', true);
|
||||
testJSON('[1,"', true);
|
||||
testJSON('[1,"\\', true);
|
||||
|
||||
testJSON('{', true);
|
||||
testJSON('{1', true);
|
||||
testJSON('{,', true);
|
||||
testJSON('{"', true);
|
||||
testJSON('{"\\', true);
|
||||
testJSON('{"\\u', true);
|
||||
testJSON('{"\\uG', true);
|
||||
testJSON('{"\\u0', true);
|
||||
testJSON('{"\\u01', true);
|
||||
testJSON('{"\\u012', true);
|
||||
testJSON('{"\\u0123', true);
|
||||
testJSON('{"\\u0123"', true);
|
||||
testJSON('{"a"', true);
|
||||
testJSON('{"a"}', true);
|
||||
testJSON('{"a":', true);
|
||||
testJSON('{"a",}', true);
|
||||
testJSON('{"a":}', true);
|
||||
testJSON('{"a":,}', true);
|
||||
testJSON('{"a":5,}', true);
|
||||
testJSON('{"a":5,[', true);
|
||||
testJSON('{"a":5,"', true);
|
||||
testJSON('{"a":5,"', true);
|
||||
testJSON('{"a":5,"\\', true);
|
||||
testJSON("a[false ]".substring(1, 7) /* "[false" */, true);
|
||||
|
||||
testJSON('this', true);
|
||||
|
||||
testJSON('[1,{}]', false);
|
||||
testJSON('{}', false);
|
||||
testJSON('{"a":5}', false);
|
||||
testJSON('{"\\u0123":5}', false);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
|
@ -2,6 +2,11 @@ gTestsubsuite='JSON';
|
|||
|
||||
function testJSON(str, expectSyntaxError)
|
||||
{
|
||||
// Leading and trailing whitespace never affect parsing, so test the string
|
||||
// multiple times with and without whitespace around it as it's easy and can
|
||||
// potentially detect bugs.
|
||||
|
||||
// Try the provided string
|
||||
try
|
||||
{
|
||||
JSON.parse(str);
|
||||
|
@ -26,4 +31,82 @@ function testJSON(str, expectSyntaxError)
|
|||
"have parsed as JSON, exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Now try the provided string with trailing whitespace
|
||||
try
|
||||
{
|
||||
JSON.parse(str + " ");
|
||||
reportCompare(false, expectSyntaxError,
|
||||
"string <" + str + " > " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (!(e instanceof SyntaxError))
|
||||
{
|
||||
reportCompare(true, false,
|
||||
"parsing string <" + str + " > threw a non-SyntaxError " +
|
||||
"exception: " + e);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportCompare(true, expectSyntaxError,
|
||||
"string <" + str + " > " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON, exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Now try the provided string with leading whitespace
|
||||
try
|
||||
{
|
||||
JSON.parse(" " + str);
|
||||
reportCompare(false, expectSyntaxError,
|
||||
"string < " + str + "> " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (!(e instanceof SyntaxError))
|
||||
{
|
||||
reportCompare(true, false,
|
||||
"parsing string < " + str + "> threw a non-SyntaxError " +
|
||||
"exception: " + e);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportCompare(true, expectSyntaxError,
|
||||
"string < " + str + "> " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON, exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Now try the provided string with whitespace surrounding it
|
||||
try
|
||||
{
|
||||
JSON.parse(" " + str + " ");
|
||||
reportCompare(false, expectSyntaxError,
|
||||
"string < " + str + " > " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (!(e instanceof SyntaxError))
|
||||
{
|
||||
reportCompare(true, false,
|
||||
"parsing string < " + str + " > threw a non-SyntaxError " +
|
||||
"exception: " + e);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportCompare(true, expectSyntaxError,
|
||||
"string < " + str + " > " +
|
||||
"should" + (expectSyntaxError ? "n't" : "") + " " +
|
||||
"have parsed as JSON, exception: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ script eval-native-callback-is-indirect.js
|
|||
script extension-methods-reject-null-undefined-this.js
|
||||
skip-if(!xulRuntime.shell) script function-definition-with.js # needs evaluate()
|
||||
script iterator-in-catch.js
|
||||
skip-if(!xulRuntime.shell) script legacy-JSON.js # needs parseLegacyJSON
|
||||
fails script nested-delete-name-in-evalcode.js # bug 604301, at a minimum
|
||||
script proxy-strict.js
|
||||
script regress-bug567606.js
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// 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");
|
Загрузка…
Ссылка в новой задаче