Bug 462778. Fix JSON top crash. r=brendan

This commit is contained in:
Robert Sayre 2008-11-07 18:10:39 -05:00
Родитель 8b29d8ed16
Коммит d813f67054
5 изменённых файлов: 304 добавлений и 51 удалений

Просмотреть файл

@ -0,0 +1,205 @@
// returns a list of [string, object] pairs to test encoding
function getTestPairs() {
var testPairs = [
["{}", {}],
["[]", []],
['{"foo":"bar"}', {"foo":"bar"}],
['{"null":null}', {"null":null}],
['{"five":5}', {"five":5}],
['{"five":5,"six":6}', {"five":5, "six":6}],
['{"x":{"y":"z"}}', {"x":{"y":"z"}}],
['{"w":{"x":{"y":"z"}}}', {"w":{"x":{"y":"z"}}}],
['[1,2,3]', [1,2,3]],
['{"w":{"x":{"y":[1,2,3]}}}', {"w":{"x":{"y":[1,2,3]}}}],
['{"false":false}', {"false":false}],
['{"true":true}', {"true":true}],
['{"child has two members":{"this":"one","2":"and this one"}}',
{"child has two members": {"this":"one", 2:"and this one"}}],
['{"x":{"a":"b","c":{"y":"z"},"f":"g"}}',
{"x":{"a":"b","c":{"y":"z"},"f":"g"}}],
['{"x":[1,{"y":"z"},3]}', {"x":[1,{"y":"z"},3]}],
['["hmm"]', [new String("hmm")]],
['[true]', [new Boolean(true)]],
['[42]', [new Number(42)]],
['["1978-09-13T12:24:34.023Z"]', [new Date(Date.UTC(1978, 8, 13, 12, 24, 34, 23))]],
['[1,null,3]',[1,,3]],
['{"mm\\\"mm":"hmm"}',{"mm\"mm":"hmm"}],
['{"mm\\\"mm\\\"mm":"hmm"}',{"mm\"mm\"mm":"hmm"}],
['{"\\\"":"hmm"}',{'"':"hmm"}],
['{"\\\\":"hmm"}',{'\\':"hmm"}],
['{"mmm\\\\mmm":"hmm"}',{'mmm\\mmm':"hmm"}],
['{"mmm\\\\mmm\\\\mmm":"hmm"}',{'mmm\\mmm\\mmm':"hmm"}],
['{"mm\\u000bmm":"hmm"}',{"mm\u000bmm":"hmm"}],
['{"mm\\u0000mm":"hmm"}',{"mm\u0000mm":"hmm"}]
];
var x = {"free":"variable"}
testPairs.push(['{"free":"variable"}', x]);
testPairs.push(['{"y":{"free":"variable"}}', {"y":x}]);
// array prop
var x = {
a: [1,2,3]
}
testPairs.push(['{"a":[1,2,3]}', x])
var y = {
foo: function(hmm) { return hmm; }
}
testPairs.push(['{"y":{}}',{"y":y}]);
// test toJSON
var hmm = {
toJSON: function() { return {"foo":"bar"}}
}
testPairs.push(['{"hmm":{"foo":"bar"}}', {"hmm":hmm}]);
testPairs.push(['{"foo":"bar"}', hmm]); // on the root
// toJSON on prototype
var Y = function() {
this.d = "e";
}
Y.prototype = {
not:"there?",
toJSON: function() { return {"foo":"bar"}}
};
var y = new Y();
testPairs.push(['{"foo":"bar"}', y.toJSON()]);
testPairs.push(['{"foo":"bar"}', y]);
// return undefined from toJSON
var hmm = {
toJSON: function() { return; }
}
testPairs.push(['{}', {"hmm":hmm}]);
// array with named prop
var x= new Array();
x[0] = 1;
x.foo = "bar";
testPairs.push(['[1]', x]);
// prototype
var X = function() { this.a = "b" }
X.prototype = {c:"d"}
var y = new X();
testPairs.push(['{"a":"b","c":"d"}', y]);
// custom iterator: JS 1.7+
var x = {
"a": "foo",
b: "not included",
c: "bar",
"4": "qux",
__iterator__: function() { return (function() { yield "a"; yield "c"; yield 4; })() }
}
do_check_eq('{"a":"foo","c":"bar","4":"qux"}', JSON.stringify(x));
return testPairs;
}
function testStringEncode() {
var pairs = getTestPairs();
for each(pair in pairs) {
print(pair)
var nativeResult = JSON.stringify(pair[1]);
var crockfordResult = crockfordJSON.stringify(pair[1]);
do_check_eq(pair[0], nativeResult);
// Don't follow json2.js handling of non-objects
if (pair[1] && (typeof pair[1] == "object")) {
do_check_eq(crockfordResult, nativeResult);
}
}
}
function decode_strings() {
// empty object
var x = JSON.parse("{}");
do_check_eq(typeof x, "object");
// empty array
x = JSON.parse("[]");
do_check_eq(typeof x, "object");
do_check_eq(x.length, 0);
do_check_eq(x.constructor, Array);
// one element array
x = JSON.parse("[[]]");
do_check_eq(typeof x, "object");
do_check_eq(x.length, 1);
do_check_eq(x.constructor, Array);
do_check_eq(x[0].constructor, Array);
// multiple arrays
x = JSON.parse("[[],[],[]]");
do_check_eq(typeof x, "object");
do_check_eq(x.length, 3);
do_check_eq(x.constructor, Array);
do_check_eq(x[0].constructor, Array);
do_check_eq(x[1].constructor, Array);
do_check_eq(x[2].constructor, Array);
// array key/value
x = JSON.parse('{"foo":[]}');
do_check_eq(typeof x, "object");
do_check_eq(typeof x.foo, "object");
do_check_eq(x.foo.constructor, Array);
x = JSON.parse('{"foo":[], "bar":[]}');
do_check_eq(typeof x, "object");
do_check_eq(typeof x.foo, "object");
do_check_eq(x.foo.constructor, Array);
do_check_eq(typeof x.bar, "object");
do_check_eq(x.bar.constructor, Array);
// nesting
x = JSON.parse('{"foo":[{}]}');
do_check_eq(x.foo.constructor, Array);
do_check_eq(x.foo.length, 1);
do_check_eq(typeof x.foo[0], "object");
x = JSON.parse('{"foo":[{"foo":[{"foo":{}}]}]}');
do_check_eq(x.foo[0].foo[0].foo.constructor, Object);
x = JSON.parse('{"foo":[{"foo":[{"foo":[]}]}]}');
do_check_eq(x.foo[0].foo[0].foo.constructor, Array);
// strings
x = JSON.parse('{"foo":"bar"}');
do_check_eq(x.foo, "bar");
x = JSON.parse('["foo", "bar", "baz"]');
do_check_eq(x[0], "foo");
do_check_eq(x[1], "bar");
do_check_eq(x[2], "baz");
// numbers
x = JSON.parse('{"foo":5.5, "bar":5}');
do_check_eq(x.foo, 5.5);
do_check_eq(x.bar, 5);
// keywords
x = JSON.parse('{"foo": true, "bar":false, "baz":null}');
do_check_eq(x.foo, true);
do_check_eq(x.bar, false);
do_check_eq(x.baz, null);
// short escapes
x = JSON.parse('{"foo": "\\"", "bar":"\\\\", "baz":"\\b","qux":"\\f", "quux":"\\n", "quuux":"\\r","quuuux":"\\t"}');
do_check_eq(x.foo, '"');
do_check_eq(x.bar, '\\');
do_check_eq(x.baz, '\b');
do_check_eq(x.qux, '\f');
do_check_eq(x.quux, "\n");
do_check_eq(x.quuux, "\r");
do_check_eq(x.quuuux, "\t");
// unicode escape
x = JSON.parse('{"foo":"hmm\\u006dmm"}');
do_check_eq("hmm\u006dmm", x.foo);
x = JSON.parse('{"JSON Test Pattern pass3": {"The outermost value": "must be an object or array.","In this test": "It is an object." }}');
}
function run_test() {
testStringEncode();
decode_strings();
}

Просмотреть файл

@ -83,14 +83,16 @@ js_json_parse(JSContext *cx, uintN argc, jsval *vp)
}
if (!ok)
JS_ReportError(cx, "Error parsing JSON.");
JS_ReportError(cx, "Error parsing JSON");
return ok;
}
struct StringifyClosure
class StringifyClosure : JSAutoTempValueRooter
{
StringifyClosure(JSContext *aCx, jsval *str) : cx(aCx), s(str)
public:
StringifyClosure(JSContext *cx, size_t len, jsval *vec)
: JSAutoTempValueRooter(cx, len, vec), cx(cx), s(vec)
{
}
@ -102,16 +104,20 @@ static JSBool
WriteCallback(const jschar *buf, uint32 len, void *data)
{
StringifyClosure *sc = static_cast<StringifyClosure*>(data);
JSString *s1 = JSVAL_TO_STRING(*sc->s);
JSString *s1 = JSVAL_TO_STRING(sc->s[0]);
JSString *s2 = js_NewStringCopyN(sc->cx, buf, len);
if (!s2)
return JS_FALSE;
sc->s[1] = STRING_TO_JSVAL(s2);
s1 = js_ConcatStrings(sc->cx, s1, s2);
if (!s1)
return JS_FALSE;
*sc->s = STRING_TO_JSVAL(s1);
sc->s[0] = STRING_TO_JSVAL(s1);
sc->s[1] = JSVAL_VOID;
return JS_TRUE;
}
@ -126,13 +132,15 @@ js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
return JS_FALSE;
// Only use objects and arrays as the root for now
jsval v = OBJECT_TO_JSVAL(obj);
JSBool ok = js_TryJSON(cx, &v);
*vp = OBJECT_TO_JSVAL(obj);
JSBool ok = js_TryJSON(cx, vp);
JSType type;
if (!(ok && !JSVAL_IS_PRIMITIVE(v) &&
(type = JS_TypeOfValue(cx, v)) != JSTYPE_FUNCTION &&
type != JSTYPE_XML)) {
JS_ReportError(cx, "Invalid argument.");
if (!ok ||
JSVAL_IS_PRIMITIVE(*vp) ||
((type = JS_TypeOfValue(cx, *vp)) == JSTYPE_FUNCTION ||
type == JSTYPE_XML)) {
JS_ReportError(cx, "Invalid argument");
return JS_FALSE;
}
@ -141,10 +149,10 @@ js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
ok = JS_FALSE;
if (ok) {
jsval sv = STRING_TO_JSVAL(s);
StringifyClosure sc(cx, &sv);
JSAutoTempValueRooter tvr(cx, 1, sc.s);
ok = js_Stringify(cx, &v, NULL, &WriteCallback, &sc, 0);
jsval vec[2] = {STRING_TO_JSVAL(s), JSVAL_VOID};
StringifyClosure sc(cx, 2, vec);
JSAutoTempValueRooter resultTvr(cx, 1, sc.s);
ok = js_Stringify(cx, vp, NULL, &WriteCallback, &sc, 0);
*vp = *sc.s;
}
@ -191,8 +199,11 @@ write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar
char ubuf[10];
unsigned int len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]);
JS_ASSERT(len == 2);
// TODO: don't allocate a JSString just to inflate (js_InflateStringToBuffer on static?)
// FIXME: bug 463715 - don't allocate a JSString just to inflate
// (js_InflateStringToBuffer on static?)
JSString *us = JS_NewStringCopyN(cx, ubuf, len);
if (!us)
return JS_FALSE;
if (!callback(JS_GetStringChars(us), len, data))
return JS_FALSE;
mark = i + 1;
@ -246,7 +257,8 @@ js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
if (isArray) {
if ((jsuint)i >= length)
break;
ok = JS_GetElement(cx, obj, i++, &outputValue);
ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue);
i++;
} else {
ok = js_CallIteratorNext(cx, iterObj, &key);
if (!ok)
@ -359,6 +371,11 @@ js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
break;
}
if (!outputString) {
ok = JS_FALSE;
break;
}
ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
}
} while (ok);
@ -370,7 +387,7 @@ js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
}
if (!ok) {
JS_ReportError(cx, "Error during JSON encoding.");
JS_ReportError(cx, "Error during JSON encoding");
return JS_FALSE;
}
@ -410,7 +427,12 @@ js_BeginJSONParse(JSContext *cx, jsval *rootVal)
jp->statep = jp->stateStack;
*jp->statep = JSON_PARSE_STATE_INIT;
jp->rootVal = rootVal;
jp->objectKey = NULL;
jp->objectKey = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
if (!jp->objectKey)
goto bad;
js_InitStringBuffer(jp->objectKey);
jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
if (!jp->buffer)
goto bad;
@ -429,10 +451,14 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp)
if (!jp)
return JS_TRUE;
if (jp->objectKey)
js_FinishStringBuffer(jp->objectKey);
JS_free(cx, jp->objectKey);
if (jp->buffer)
js_FinishStringBuffer(jp->buffer);
JS_free(cx, jp->buffer);
if (!js_RemoveRoot(cx->runtime, &jp->objectStack))
return JS_FALSE;
JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
@ -480,12 +506,16 @@ PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
if (OBJ_IS_ARRAY(cx, parent)) {
jsuint len;
ok = js_GetLengthProperty(cx, parent, &len);
if (ok)
ok = JS_SetElement(cx, parent, len, &value);
if (ok) {
ok = OBJ_DEFINE_PROPERTY(cx, parent, INT_TO_JSID(len), value,
NULL, NULL, JSPROP_ENUMERATE, NULL);
}
} else {
ok = JS_DefineUCProperty(cx, parent, JS_GetStringChars(jp->objectKey),
JS_GetStringLength(jp->objectKey), value,
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);
}
return ok;
@ -501,25 +531,33 @@ PushObject(JSContext *cx, JSONParser *jp, JSObject *obj)
return JS_FALSE; // decoding error
jsval v = OBJECT_TO_JSVAL(obj);
JSAutoTempValueRooter tvr(cx, v);
// Check if this is the root object
if (len == 0) {
*jp->rootVal = v;
if (!JS_SetElement(cx, jp->objectStack, 0, jp->rootVal))
// This property must be enumerable to keep the array dense
if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(0), *jp->rootVal,
NULL, NULL, JSPROP_ENUMERATE, NULL)) {
return JS_FALSE;
}
return JS_TRUE;
}
jsval p;
if (!JS_GetElement(cx, jp->objectStack, len - 1, &p))
if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &p))
return JS_FALSE;
JS_ASSERT(JSVAL_IS_OBJECT(p));
JSObject *parent = JSVAL_TO_OBJECT(p);
if (!PushValue(cx, jp, parent, OBJECT_TO_JSVAL(obj)))
return JS_FALSE;
if (!JS_SetElement(cx, jp->objectStack, len, &v))
// This property must be enumerable to keep the array dense
if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len), v,
NULL, NULL, JSPROP_ENUMERATE, NULL)) {
return JS_FALSE;
}
return JS_TRUE;
}
@ -532,7 +570,7 @@ GetTopOfObjectStack(JSContext *cx, JSONParser *jp)
return NULL;
jsval o;
if (!JS_GetElement(cx, jp->objectStack, length - 1, &o))
if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(length - 1), &o))
return NULL;
JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));
@ -633,26 +671,29 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
}
static JSBool
HandleData(JSContext *cx, JSONParser *jp, JSONDataType type, const jschar *buf, uint32 len)
HandleData(JSContext *cx, JSONParser *jp, JSONDataType type)
{
JSBool ok = JS_FALSE;
if (!STRING_BUFFER_OK(jp->buffer))
return JS_FALSE;
switch (type) {
case JSON_DATA_STRING:
ok = HandleString(cx, jp, buf, len);
ok = HandleString(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
break;
case JSON_DATA_KEYSTRING:
jp->objectKey = js_NewStringCopyN(cx, buf, len);
ok = JS_TRUE;
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, buf, len);
ok = HandleNumber(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
break;
case JSON_DATA_KEYWORD:
ok = HandleKeyword(cx, jp, buf, len);
ok = HandleKeyword(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
break;
default:
@ -783,7 +824,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len
} else {
jdt = JSON_DATA_STRING;
}
if (!HandleData(cx, jp, jdt, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
if (!HandleData(cx, jp, jdt))
return JS_FALSE;
} else if (c == '\\') {
*jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;
@ -845,7 +886,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len
if (!PopState(jp))
return JS_FALSE;
if (!HandleData(cx, jp, JSON_DATA_KEYWORD, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
if (!HandleData(cx, jp, JSON_DATA_KEYWORD))
return JS_FALSE;
}
break;
@ -858,7 +899,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len
i--;
if (!PopState(jp))
return JS_FALSE;
if (!HandleData(cx, jp, JSON_DATA_NUMBER, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
if (!HandleData(cx, jp, JSON_DATA_NUMBER))
return JS_FALSE;
}
break;

Просмотреть файл

@ -88,7 +88,7 @@ struct JSONParser {
JSONParserState *statep;
JSONParserState stateStack[JSON_MAX_DEPTH];
jsval *rootVal;
JSString *objectKey;
JSStringBuffer *objectKey;
JSStringBuffer *buffer;
JSObject *objectStack;
};

Просмотреть файл

@ -749,6 +749,22 @@ js_AppendChar(JSStringBuffer *sb, jschar c)
sb->ptr = bp;
}
void
js_AppendUCString(JSStringBuffer *sb, const jschar *buf, uintN len)
{
jschar *bp;
if (!STRING_BUFFER_OK(sb))
return;
if (len == 0 || !ENSURE_STRING_BUFFER(sb, len))
return;
bp = sb->ptr;
js_strncpy(bp, buf, len);
bp += len;
*bp = 0;
sb->ptr = bp;
}
#if JS_HAS_XML_SUPPORT
void
@ -786,19 +802,7 @@ js_AppendCString(JSStringBuffer *sb, const char *asciiz)
void
js_AppendJSString(JSStringBuffer *sb, JSString *str)
{
size_t length;
jschar *bp;
if (!STRING_BUFFER_OK(sb))
return;
length = JSSTRING_LENGTH(str);
if (length == 0 || !ENSURE_STRING_BUFFER(sb, length))
return;
bp = sb->ptr;
js_strncpy(bp, JSSTRING_CHARS(str), length);
bp += length;
*bp = 0;
sb->ptr = bp;
js_AppendUCString(sb, JSSTRING_CHARS(str), JSSTRING_LENGTH(str));
}
static JSBool

Просмотреть файл

@ -181,6 +181,9 @@ js_RepeatChar(JSStringBuffer *sb, jschar c, uintN count);
extern void
js_AppendCString(JSStringBuffer *sb, const char *asciiz);
extern void
js_AppendUCString(JSStringBuffer *sb, const jschar *buf, uintN len);
extern void
js_AppendJSString(JSStringBuffer *sb, JSString *str);