michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "jsstr.h" michael@0: michael@0: #include "jsapi-tests/tests.h" michael@0: michael@0: using namespace js; michael@0: michael@0: class AutoInflatedString { michael@0: JSContext * const cx; michael@0: jschar *chars_; michael@0: size_t length_; michael@0: michael@0: public: michael@0: AutoInflatedString(JSContext *cx) : cx(cx), chars_(nullptr), length_(0) { } michael@0: ~AutoInflatedString() { michael@0: JS_free(cx, chars_); michael@0: } michael@0: michael@0: template void operator=(const char (&str)[N]) { michael@0: length_ = N - 1; michael@0: chars_ = InflateString(cx, str, &length_); michael@0: if (!chars_) michael@0: abort(); michael@0: } michael@0: michael@0: const jschar *chars() const { return chars_; } michael@0: size_t length() const { return length_; } michael@0: }; michael@0: michael@0: BEGIN_TEST(testParseJSON_success) michael@0: { michael@0: // Primitives michael@0: JS::RootedValue expected(cx); michael@0: expected = JSVAL_TRUE; michael@0: CHECK(TryParse(cx, "true", expected)); michael@0: michael@0: expected = JSVAL_FALSE; michael@0: CHECK(TryParse(cx, "false", expected)); michael@0: michael@0: expected = JSVAL_NULL; michael@0: CHECK(TryParse(cx, "null", expected)); michael@0: michael@0: expected = INT_TO_JSVAL(0); michael@0: CHECK(TryParse(cx, "0", expected)); michael@0: michael@0: expected = INT_TO_JSVAL(1); michael@0: CHECK(TryParse(cx, "1", expected)); michael@0: michael@0: expected = INT_TO_JSVAL(-1); michael@0: CHECK(TryParse(cx, "-1", expected)); michael@0: michael@0: expected = DOUBLE_TO_JSVAL(1); michael@0: CHECK(TryParse(cx, "1", expected)); michael@0: michael@0: expected = DOUBLE_TO_JSVAL(1.75); michael@0: CHECK(TryParse(cx, "1.75", expected)); michael@0: michael@0: expected = DOUBLE_TO_JSVAL(9e9); michael@0: CHECK(TryParse(cx, "9e9", expected)); michael@0: michael@0: expected = DOUBLE_TO_JSVAL(std::numeric_limits::infinity()); michael@0: CHECK(TryParse(cx, "9e99999", expected)); michael@0: michael@0: JS::Rooted str(cx); michael@0: michael@0: const jschar emptystr[] = { '\0' }; michael@0: str = js_NewStringCopyN(cx, emptystr, 0); michael@0: CHECK(str); michael@0: expected = STRING_TO_JSVAL(str); michael@0: CHECK(TryParse(cx, "\"\"", expected)); michael@0: michael@0: const jschar nullstr[] = { '\0' }; michael@0: str = NewString(cx, nullstr); michael@0: CHECK(str); michael@0: expected = STRING_TO_JSVAL(str); michael@0: CHECK(TryParse(cx, "\"\\u0000\"", expected)); michael@0: michael@0: const jschar backstr[] = { '\b' }; michael@0: str = NewString(cx, backstr); michael@0: CHECK(str); michael@0: expected = STRING_TO_JSVAL(str); michael@0: CHECK(TryParse(cx, "\"\\b\"", expected)); michael@0: CHECK(TryParse(cx, "\"\\u0008\"", expected)); michael@0: michael@0: const jschar newlinestr[] = { '\n', }; michael@0: str = NewString(cx, newlinestr); michael@0: CHECK(str); michael@0: expected = STRING_TO_JSVAL(str); michael@0: CHECK(TryParse(cx, "\"\\n\"", expected)); michael@0: CHECK(TryParse(cx, "\"\\u000A\"", expected)); michael@0: michael@0: michael@0: // Arrays michael@0: JS::RootedValue v(cx), v2(cx); michael@0: JS::RootedObject obj(cx); michael@0: michael@0: CHECK(Parse(cx, "[]", &v)); michael@0: CHECK(!JSVAL_IS_PRIMITIVE(v)); michael@0: obj = JSVAL_TO_OBJECT(v); michael@0: CHECK(JS_IsArrayObject(cx, obj)); michael@0: CHECK(JS_GetProperty(cx, obj, "length", &v2)); michael@0: CHECK_SAME(v2, JSVAL_ZERO); michael@0: michael@0: CHECK(Parse(cx, "[1]", &v)); michael@0: CHECK(!JSVAL_IS_PRIMITIVE(v)); michael@0: obj = JSVAL_TO_OBJECT(v); michael@0: CHECK(JS_IsArrayObject(cx, obj)); michael@0: CHECK(JS_GetProperty(cx, obj, "0", &v2)); michael@0: CHECK_SAME(v2, JSVAL_ONE); michael@0: CHECK(JS_GetProperty(cx, obj, "length", &v2)); michael@0: CHECK_SAME(v2, JSVAL_ONE); michael@0: michael@0: michael@0: // Objects michael@0: CHECK(Parse(cx, "{}", &v)); michael@0: CHECK(!JSVAL_IS_PRIMITIVE(v)); michael@0: obj = JSVAL_TO_OBJECT(v); michael@0: CHECK(!JS_IsArrayObject(cx, obj)); michael@0: michael@0: CHECK(Parse(cx, "{ \"f\": 17 }", &v)); michael@0: CHECK(!JSVAL_IS_PRIMITIVE(v)); michael@0: obj = JSVAL_TO_OBJECT(v); michael@0: CHECK(!JS_IsArrayObject(cx, obj)); michael@0: CHECK(JS_GetProperty(cx, obj, "f", &v2)); michael@0: CHECK_SAME(v2, INT_TO_JSVAL(17)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template static JSFlatString * michael@0: NewString(JSContext *cx, const jschar (&chars)[N]) michael@0: { michael@0: return js_NewStringCopyN(cx, chars, N); michael@0: } michael@0: michael@0: template inline bool michael@0: Parse(JSContext *cx, const char (&input)[N], JS::MutableHandleValue vp) michael@0: { michael@0: AutoInflatedString str(cx); michael@0: str = input; michael@0: CHECK(JS_ParseJSON(cx, str.chars(), str.length(), vp)); michael@0: return true; michael@0: } michael@0: michael@0: template inline bool michael@0: TryParse(JSContext *cx, const char (&input)[N], JS::HandleValue expected) michael@0: { michael@0: AutoInflatedString str(cx); michael@0: RootedValue v(cx); michael@0: str = input; michael@0: CHECK(JS_ParseJSON(cx, str.chars(), str.length(), &v)); michael@0: CHECK_SAME(v, expected); michael@0: return true; michael@0: } michael@0: END_TEST(testParseJSON_success) michael@0: michael@0: BEGIN_TEST(testParseJSON_error) michael@0: { michael@0: CHECK(Error(cx, "" , "1", "1")); michael@0: CHECK(Error(cx, "\n" , "2", "1")); michael@0: CHECK(Error(cx, "\r" , "2", "1")); michael@0: CHECK(Error(cx, "\r\n" , "2", "1")); michael@0: michael@0: CHECK(Error(cx, "[" , "1", "2")); michael@0: CHECK(Error(cx, "[,]" , "1", "2")); michael@0: CHECK(Error(cx, "[1,]" , "1", "4")); michael@0: CHECK(Error(cx, "{a:2}" , "1", "2")); michael@0: CHECK(Error(cx, "{\"a\":2,}" , "1", "8")); michael@0: CHECK(Error(cx, "]" , "1", "1")); michael@0: CHECK(Error(cx, "\"" , "1", "2")); michael@0: CHECK(Error(cx, "{]" , "1", "2")); michael@0: CHECK(Error(cx, "[}" , "1", "2")); michael@0: CHECK(Error(cx, "'wrongly-quoted string'" , "1", "1")); michael@0: michael@0: CHECK(Error(cx, "{\"a\":2 \n b:3}" , "2", "2")); michael@0: CHECK(Error(cx, "\n[" , "2", "2")); michael@0: CHECK(Error(cx, "\n[,]" , "2", "2")); michael@0: CHECK(Error(cx, "\n[1,]" , "2", "4")); michael@0: CHECK(Error(cx, "\n{a:2}" , "2", "2")); michael@0: CHECK(Error(cx, "\n{\"a\":2,}" , "2", "8")); michael@0: CHECK(Error(cx, "\n]" , "2", "1")); michael@0: CHECK(Error(cx, "\"bad string\n\"" , "1", "12")); michael@0: CHECK(Error(cx, "\r'wrongly-quoted string'" , "2", "1")); michael@0: CHECK(Error(cx, "\n\"" , "2", "2")); michael@0: CHECK(Error(cx, "\n{]" , "2", "2")); michael@0: CHECK(Error(cx, "\n[}" , "2", "2")); michael@0: CHECK(Error(cx, "{\"a\":[2,3],\n\"b\":,5,6}" , "2", "5")); michael@0: michael@0: CHECK(Error(cx, "{\"a\":2 \r b:3}" , "2", "2")); michael@0: CHECK(Error(cx, "\r[" , "2", "2")); michael@0: CHECK(Error(cx, "\r[,]" , "2", "2")); michael@0: CHECK(Error(cx, "\r[1,]" , "2", "4")); michael@0: CHECK(Error(cx, "\r{a:2}" , "2", "2")); michael@0: CHECK(Error(cx, "\r{\"a\":2,}" , "2", "8")); michael@0: CHECK(Error(cx, "\r]" , "2", "1")); michael@0: CHECK(Error(cx, "\"bad string\r\"" , "1", "12")); michael@0: CHECK(Error(cx, "\r'wrongly-quoted string'" , "2", "1")); michael@0: CHECK(Error(cx, "\r\"" , "2", "2")); michael@0: CHECK(Error(cx, "\r{]" , "2", "2")); michael@0: CHECK(Error(cx, "\r[}" , "2", "2")); michael@0: CHECK(Error(cx, "{\"a\":[2,3],\r\"b\":,5,6}" , "2", "5")); michael@0: michael@0: CHECK(Error(cx, "{\"a\":2 \r\n b:3}" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n[" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n[,]" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n[1,]" , "2", "4")); michael@0: CHECK(Error(cx, "\r\n{a:2}" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n{\"a\":2,}" , "2", "8")); michael@0: CHECK(Error(cx, "\r\n]" , "2", "1")); michael@0: CHECK(Error(cx, "\"bad string\r\n\"" , "1", "12")); michael@0: CHECK(Error(cx, "\r\n'wrongly-quoted string'" , "2", "1")); michael@0: CHECK(Error(cx, "\r\n\"" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n{]" , "2", "2")); michael@0: CHECK(Error(cx, "\r\n[}" , "2", "2")); michael@0: CHECK(Error(cx, "{\"a\":[2,3],\r\n\"b\":,5,6}" , "2", "5")); michael@0: michael@0: CHECK(Error(cx, "\n\"bad string\n\"" , "2", "12")); michael@0: CHECK(Error(cx, "\r\"bad string\r\"" , "2", "12")); michael@0: CHECK(Error(cx, "\r\n\"bad string\r\n\"" , "2", "12")); michael@0: michael@0: CHECK(Error(cx, "{\n\"a\":[2,3],\r\"b\":,5,6}" , "3", "5")); michael@0: CHECK(Error(cx, "{\r\"a\":[2,3],\n\"b\":,5,6}" , "3", "5")); michael@0: CHECK(Error(cx, "[\"\\t\\q" , "1", "6")); michael@0: CHECK(Error(cx, "[\"\\t\x00" , "1", "5")); michael@0: CHECK(Error(cx, "[\"\\t\x01" , "1", "5")); michael@0: CHECK(Error(cx, "[\"\\t\\\x00" , "1", "6")); michael@0: CHECK(Error(cx, "[\"\\t\\\x01" , "1", "6")); michael@0: michael@0: // Unicode escape errors are messy. The first bad character could be michael@0: // non-hexadecimal, or it could be absent entirely. Include tests where michael@0: // there's a bad character, followed by zero to as many characters as are michael@0: // needed to form a complete Unicode escape sequence, plus one. (The extra michael@0: // characters beyond are valuable because our implementation checks for michael@0: // too-few subsequent characters first, before checking for subsequent michael@0: // non-hexadecimal characters. So \u, \u0, \u00, and michael@0: // \u000 are all *detected* as invalid by the same code path, but the michael@0: // process of computing the first invalid character follows a different michael@0: // code path for each. And \uQQQQ, \u0QQQ, \u00QQ, and \u000Q are detected michael@0: // as invalid by the same code path [ignoring which precise subexpression michael@0: // triggers failure of a single condition], but the computation of the michael@0: // first invalid character follows a different code path for each.) michael@0: CHECK(Error(cx, "[\"\\t\\u" , "1", "7")); michael@0: CHECK(Error(cx, "[\"\\t\\uZ" , "1", "7")); michael@0: CHECK(Error(cx, "[\"\\t\\uZZ" , "1", "7")); michael@0: CHECK(Error(cx, "[\"\\t\\uZZZ" , "1", "7")); michael@0: CHECK(Error(cx, "[\"\\t\\uZZZZ" , "1", "7")); michael@0: CHECK(Error(cx, "[\"\\t\\uZZZZZ" , "1", "7")); michael@0: michael@0: CHECK(Error(cx, "[\"\\t\\u0" , "1", "8")); michael@0: CHECK(Error(cx, "[\"\\t\\u0Z" , "1", "8")); michael@0: CHECK(Error(cx, "[\"\\t\\u0ZZ" , "1", "8")); michael@0: CHECK(Error(cx, "[\"\\t\\u0ZZZ" , "1", "8")); michael@0: CHECK(Error(cx, "[\"\\t\\u0ZZZZ" , "1", "8")); michael@0: michael@0: CHECK(Error(cx, "[\"\\t\\u00" , "1", "9")); michael@0: CHECK(Error(cx, "[\"\\t\\u00Z" , "1", "9")); michael@0: CHECK(Error(cx, "[\"\\t\\u00ZZ" , "1", "9")); michael@0: CHECK(Error(cx, "[\"\\t\\u00ZZZ" , "1", "9")); michael@0: michael@0: CHECK(Error(cx, "[\"\\t\\u000" , "1", "10")); michael@0: CHECK(Error(cx, "[\"\\t\\u000Z" , "1", "10")); michael@0: CHECK(Error(cx, "[\"\\t\\u000ZZ" , "1", "10")); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template inline bool michael@0: Error(JSContext *cx, const char (&input)[N], const char (&expectedLine)[M], michael@0: const char (&expectedColumn)[L]) michael@0: { michael@0: AutoInflatedString str(cx), line(cx), column(cx); michael@0: RootedValue dummy(cx); michael@0: str = input; michael@0: michael@0: ContextPrivate p = {0, 0}; michael@0: CHECK(!JS_GetContextPrivate(cx)); michael@0: JS_SetContextPrivate(cx, &p); michael@0: JSErrorReporter old = JS_SetErrorReporter(cx, ReportJSONError); michael@0: bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy); michael@0: JS_SetErrorReporter(cx, old); michael@0: JS_SetContextPrivate(cx, nullptr); michael@0: michael@0: CHECK(!ok); michael@0: CHECK(!p.unexpectedErrorCount); michael@0: CHECK(p.expectedErrorCount == 1); michael@0: column = expectedColumn; michael@0: CHECK(js_strcmp(column.chars(), p.column) == 0); michael@0: line = expectedLine; michael@0: CHECK(js_strcmp(line.chars(), p.line) == 0); michael@0: michael@0: /* We do not execute JS, so there should be no exception thrown. */ michael@0: CHECK(!JS_IsExceptionPending(cx)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: struct ContextPrivate { michael@0: static const size_t MaxSize = sizeof("4294967295"); michael@0: unsigned unexpectedErrorCount; michael@0: unsigned expectedErrorCount; michael@0: jschar column[MaxSize]; michael@0: jschar line[MaxSize]; michael@0: }; michael@0: michael@0: static void michael@0: ReportJSONError(JSContext *cx, const char *message, JSErrorReport *report) michael@0: { michael@0: ContextPrivate *p = static_cast(JS_GetContextPrivate(cx)); michael@0: // Although messageArgs[1] and messageArgs[2] are jschar*, we cast them to char* michael@0: // here because JSONParser::error() stores char* strings in them. michael@0: js_strncpy(p->line, report->messageArgs[1], js_strlen(report->messageArgs[1])); michael@0: js_strncpy(p->column, report->messageArgs[2], js_strlen(report->messageArgs[2])); michael@0: if (report->errorNumber == JSMSG_JSON_BAD_PARSE) michael@0: p->expectedErrorCount++; michael@0: else michael@0: p->unexpectedErrorCount++; michael@0: } michael@0: michael@0: END_TEST(testParseJSON_error) michael@0: michael@0: static bool michael@0: Censor(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JS_ASSERT(args.length() == 2); michael@0: #ifdef DEBUG michael@0: JS_ASSERT(args[0].isString()); michael@0: #endif michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: BEGIN_TEST(testParseJSON_reviver) michael@0: { michael@0: JSFunction *fun = JS_NewFunction(cx, Censor, 0, 0, global, "censor"); michael@0: CHECK(fun); michael@0: michael@0: JS::RootedValue filter(cx, OBJECT_TO_JSVAL(JS_GetFunctionObject(fun))); michael@0: michael@0: CHECK(TryParse(cx, "true", filter)); michael@0: CHECK(TryParse(cx, "false", filter)); michael@0: CHECK(TryParse(cx, "null", filter)); michael@0: CHECK(TryParse(cx, "1", filter)); michael@0: CHECK(TryParse(cx, "1.75", filter)); michael@0: CHECK(TryParse(cx, "[]", filter)); michael@0: CHECK(TryParse(cx, "[1]", filter)); michael@0: CHECK(TryParse(cx, "{}", filter)); michael@0: return true; michael@0: } michael@0: michael@0: template inline bool michael@0: TryParse(JSContext *cx, const char (&input)[N], JS::HandleValue filter) michael@0: { michael@0: AutoInflatedString str(cx); michael@0: JS::RootedValue v(cx); michael@0: str = input; michael@0: CHECK(JS_ParseJSONWithReviver(cx, str.chars(), str.length(), filter, &v)); michael@0: CHECK_SAME(v, JSVAL_NULL); michael@0: return true; michael@0: } michael@0: END_TEST(testParseJSON_reviver)