diff options
Diffstat (limited to 'js/src/jsapi-tests/testParseJSON.cpp')
-rw-r--r-- | js/src/jsapi-tests/testParseJSON.cpp | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testParseJSON.cpp b/js/src/jsapi-tests/testParseJSON.cpp new file mode 100644 index 0000000000..29016ae739 --- /dev/null +++ b/js/src/jsapi-tests/testParseJSON.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <limits> +#include <string.h> + +#include "js/Array.h" // JS::IsArrayObject +#include "js/Exception.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/JSON.h" +#include "js/MemoryFunctions.h" +#include "js/Printf.h" +#include "jsapi-tests/tests.h" + +using namespace js; + +class AutoInflatedString { + JSContext* const cx; + char16_t* chars_; + size_t length_; + + public: + explicit AutoInflatedString(JSContext* cx) + : cx(cx), chars_(nullptr), length_(0) {} + ~AutoInflatedString() { JS_free(cx, chars_); } + + template <size_t N> + void operator=(const char (&str)[N]) { + length_ = N - 1; + chars_ = InflateString(cx, str, length_); + if (!chars_) { + abort(); + } + } + + void operator=(const char* str) { + length_ = strlen(str); + chars_ = InflateString(cx, str, length_); + if (!chars_) { + abort(); + } + } + + const char16_t* chars() const { return chars_; } + size_t length() const { return length_; } +}; + +BEGIN_TEST(testParseJSON_success) { + // Primitives + JS::RootedValue expected(cx); + expected = JS::TrueValue(); + CHECK(TryParse(cx, "true", expected)); + + expected = JS::FalseValue(); + CHECK(TryParse(cx, "false", expected)); + + expected = JS::NullValue(); + CHECK(TryParse(cx, "null", expected)); + + expected.setInt32(0); + CHECK(TryParse(cx, "0", expected)); + + expected.setInt32(1); + CHECK(TryParse(cx, "1", expected)); + + expected.setInt32(-1); + CHECK(TryParse(cx, "-1", expected)); + + expected.setDouble(1); + CHECK(TryParse(cx, "1", expected)); + + expected.setDouble(1.75); + CHECK(TryParse(cx, "1.75", expected)); + + expected.setDouble(9e9); + CHECK(TryParse(cx, "9e9", expected)); + + expected.setDouble(std::numeric_limits<double>::infinity()); + CHECK(TryParse(cx, "9e99999", expected)); + + JS::Rooted<JSLinearString*> str(cx); + + const char16_t emptystr[] = {'\0'}; + str = js::NewStringCopyN<CanGC>(cx, emptystr, 0); + CHECK(str); + expected = JS::StringValue(str); + CHECK(TryParse(cx, "\"\"", expected)); + + const char16_t nullstr[] = {'\0'}; + str = NewString(cx, nullstr); + CHECK(str); + expected = JS::StringValue(str); + CHECK(TryParse(cx, "\"\\u0000\"", expected)); + + const char16_t backstr[] = {'\b'}; + str = NewString(cx, backstr); + CHECK(str); + expected = JS::StringValue(str); + CHECK(TryParse(cx, "\"\\b\"", expected)); + CHECK(TryParse(cx, "\"\\u0008\"", expected)); + + const char16_t newlinestr[] = { + '\n', + }; + str = NewString(cx, newlinestr); + CHECK(str); + expected = JS::StringValue(str); + CHECK(TryParse(cx, "\"\\n\"", expected)); + CHECK(TryParse(cx, "\"\\u000A\"", expected)); + + // Arrays + JS::RootedValue v(cx), v2(cx); + JS::RootedObject obj(cx); + + bool isArray; + + CHECK(Parse(cx, "[]", &v)); + CHECK(v.isObject()); + obj = &v.toObject(); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(isArray); + CHECK(JS_GetProperty(cx, obj, "length", &v2)); + CHECK(v2.isInt32(0)); + + CHECK(Parse(cx, "[1]", &v)); + CHECK(v.isObject()); + obj = &v.toObject(); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(isArray); + CHECK(JS_GetProperty(cx, obj, "0", &v2)); + CHECK(v2.isInt32(1)); + CHECK(JS_GetProperty(cx, obj, "length", &v2)); + CHECK(v2.isInt32(1)); + + // Objects + CHECK(Parse(cx, "{}", &v)); + CHECK(v.isObject()); + obj = &v.toObject(); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(!isArray); + + CHECK(Parse(cx, "{ \"f\": 17 }", &v)); + CHECK(v.isObject()); + obj = &v.toObject(); + CHECK(JS::IsArrayObject(cx, obj, &isArray)); + CHECK(!isArray); + CHECK(JS_GetProperty(cx, obj, "f", &v2)); + CHECK(v2.isInt32(17)); + + return true; +} + +template <size_t N> +static JSLinearString* NewString(JSContext* cx, const char16_t (&chars)[N]) { + return js::NewStringCopyN<CanGC>(cx, chars, N); +} + +template <size_t N> +inline bool Parse(JSContext* cx, const char (&input)[N], + JS::MutableHandleValue vp) { + AutoInflatedString str(cx); + str = input; + CHECK(JS_ParseJSON(cx, str.chars(), str.length(), vp)); + return true; +} + +template <size_t N> +inline bool TryParse(JSContext* cx, const char (&input)[N], + JS::HandleValue expected) { + AutoInflatedString str(cx); + RootedValue v(cx); + str = input; + CHECK(JS_ParseJSON(cx, str.chars(), str.length(), &v)); + CHECK_SAME(v, expected); + return true; +} +END_TEST(testParseJSON_success) + +BEGIN_TEST(testParseJSON_error) { + CHECK(Error(cx, "", 1, 1)); + CHECK(Error(cx, "\n", 2, 1)); + CHECK(Error(cx, "\r", 2, 1)); + CHECK(Error(cx, "\r\n", 2, 1)); + + CHECK(Error(cx, "[", 1, 2)); + CHECK(Error(cx, "[,]", 1, 2)); + CHECK(Error(cx, "[1,]", 1, 4)); + CHECK(Error(cx, "{a:2}", 1, 2)); + CHECK(Error(cx, "{\"a\":2,}", 1, 8)); + CHECK(Error(cx, "]", 1, 1)); + CHECK(Error(cx, "\"", 1, 2)); + CHECK(Error(cx, "{]", 1, 2)); + CHECK(Error(cx, "[}", 1, 2)); + CHECK(Error(cx, "'wrongly-quoted string'", 1, 1)); + + CHECK(Error(cx, "{\"a\":2 \n b:3}", 2, 2)); + CHECK(Error(cx, "\n[", 2, 2)); + CHECK(Error(cx, "\n[,]", 2, 2)); + CHECK(Error(cx, "\n[1,]", 2, 4)); + CHECK(Error(cx, "\n{a:2}", 2, 2)); + CHECK(Error(cx, "\n{\"a\":2,}", 2, 8)); + CHECK(Error(cx, "\n]", 2, 1)); + CHECK(Error(cx, "\"bad string\n\"", 1, 12)); + CHECK(Error(cx, "\r'wrongly-quoted string'", 2, 1)); + CHECK(Error(cx, "\n\"", 2, 2)); + CHECK(Error(cx, "\n{]", 2, 2)); + CHECK(Error(cx, "\n[}", 2, 2)); + CHECK(Error(cx, "{\"a\":[2,3],\n\"b\":,5,6}", 2, 5)); + + CHECK(Error(cx, "{\"a\":2 \r b:3}", 2, 2)); + CHECK(Error(cx, "\r[", 2, 2)); + CHECK(Error(cx, "\r[,]", 2, 2)); + CHECK(Error(cx, "\r[1,]", 2, 4)); + CHECK(Error(cx, "\r{a:2}", 2, 2)); + CHECK(Error(cx, "\r{\"a\":2,}", 2, 8)); + CHECK(Error(cx, "\r]", 2, 1)); + CHECK(Error(cx, "\"bad string\r\"", 1, 12)); + CHECK(Error(cx, "\r'wrongly-quoted string'", 2, 1)); + CHECK(Error(cx, "\r\"", 2, 2)); + CHECK(Error(cx, "\r{]", 2, 2)); + CHECK(Error(cx, "\r[}", 2, 2)); + CHECK(Error(cx, "{\"a\":[2,3],\r\"b\":,5,6}", 2, 5)); + + CHECK(Error(cx, "{\"a\":2 \r\n b:3}", 2, 2)); + CHECK(Error(cx, "\r\n[", 2, 2)); + CHECK(Error(cx, "\r\n[,]", 2, 2)); + CHECK(Error(cx, "\r\n[1,]", 2, 4)); + CHECK(Error(cx, "\r\n{a:2}", 2, 2)); + CHECK(Error(cx, "\r\n{\"a\":2,}", 2, 8)); + CHECK(Error(cx, "\r\n]", 2, 1)); + CHECK(Error(cx, "\"bad string\r\n\"", 1, 12)); + CHECK(Error(cx, "\r\n'wrongly-quoted string'", 2, 1)); + CHECK(Error(cx, "\r\n\"", 2, 2)); + CHECK(Error(cx, "\r\n{]", 2, 2)); + CHECK(Error(cx, "\r\n[}", 2, 2)); + CHECK(Error(cx, "{\"a\":[2,3],\r\n\"b\":,5,6}", 2, 5)); + + CHECK(Error(cx, "\n\"bad string\n\"", 2, 12)); + CHECK(Error(cx, "\r\"bad string\r\"", 2, 12)); + CHECK(Error(cx, "\r\n\"bad string\r\n\"", 2, 12)); + + CHECK(Error(cx, "{\n\"a\":[2,3],\r\"b\":,5,6}", 3, 5)); + CHECK(Error(cx, "{\r\"a\":[2,3],\n\"b\":,5,6}", 3, 5)); + CHECK(Error(cx, "[\"\\t\\q", 1, 6)); + CHECK(Error(cx, "[\"\\t\x00", 1, 5)); + CHECK(Error(cx, "[\"\\t\x01", 1, 5)); + CHECK(Error(cx, "[\"\\t\\\x00", 1, 6)); + CHECK(Error(cx, "[\"\\t\\\x01", 1, 6)); + + // Unicode escape errors are messy. The first bad character could be + // non-hexadecimal, or it could be absent entirely. Include tests where + // there's a bad character, followed by zero to as many characters as are + // needed to form a complete Unicode escape sequence, plus one. (The extra + // characters beyond are valuable because our implementation checks for + // too-few subsequent characters first, before checking for subsequent + // non-hexadecimal characters. So \u<END>, \u0<END>, \u00<END>, and + // \u000<END> are all *detected* as invalid by the same code path, but the + // process of computing the first invalid character follows a different + // code path for each. And \uQQQQ, \u0QQQ, \u00QQ, and \u000Q are detected + // as invalid by the same code path [ignoring which precise subexpression + // triggers failure of a single condition], but the computation of the + // first invalid character follows a different code path for each.) + CHECK(Error(cx, "[\"\\t\\u", 1, 7)); + CHECK(Error(cx, "[\"\\t\\uZ", 1, 7)); + CHECK(Error(cx, "[\"\\t\\uZZ", 1, 7)); + CHECK(Error(cx, "[\"\\t\\uZZZ", 1, 7)); + CHECK(Error(cx, "[\"\\t\\uZZZZ", 1, 7)); + CHECK(Error(cx, "[\"\\t\\uZZZZZ", 1, 7)); + + CHECK(Error(cx, "[\"\\t\\u0", 1, 8)); + CHECK(Error(cx, "[\"\\t\\u0Z", 1, 8)); + CHECK(Error(cx, "[\"\\t\\u0ZZ", 1, 8)); + CHECK(Error(cx, "[\"\\t\\u0ZZZ", 1, 8)); + CHECK(Error(cx, "[\"\\t\\u0ZZZZ", 1, 8)); + + CHECK(Error(cx, "[\"\\t\\u00", 1, 9)); + CHECK(Error(cx, "[\"\\t\\u00Z", 1, 9)); + CHECK(Error(cx, "[\"\\t\\u00ZZ", 1, 9)); + CHECK(Error(cx, "[\"\\t\\u00ZZZ", 1, 9)); + + CHECK(Error(cx, "[\"\\t\\u000", 1, 10)); + CHECK(Error(cx, "[\"\\t\\u000Z", 1, 10)); + CHECK(Error(cx, "[\"\\t\\u000ZZ", 1, 10)); + + return true; +} + +template <size_t N> +inline bool Error(JSContext* cx, const char (&input)[N], uint32_t expectedLine, + uint32_t expectedColumn) { + AutoInflatedString str(cx); + RootedValue dummy(cx); + str = input; + + bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy); + CHECK(!ok); + + JS::ExceptionStack exnStack(cx); + CHECK(StealPendingExceptionStack(cx, &exnStack)); + + JS::ErrorReportBuilder report(cx); + CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)); + CHECK(report.report()->errorNumber == JSMSG_JSON_BAD_PARSE); + + UniqueChars lineAndColumnASCII = + JS_smprintf("line %d column %d", expectedLine, expectedColumn); + CHECK(strstr(report.toStringResult().c_str(), lineAndColumnASCII.get()) != + nullptr); + + /* We do not execute JS, so there should be no exception thrown. */ + CHECK(!JS_IsExceptionPending(cx)); + + return true; +} +END_TEST(testParseJSON_error) + +static bool Censor(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + MOZ_RELEASE_ASSERT(args.length() == 2); + MOZ_RELEASE_ASSERT(args[0].isString()); + args.rval().setNull(); + return true; +} + +BEGIN_TEST(testParseJSON_reviver) { + JSFunction* fun = JS_NewFunction(cx, Censor, 0, 0, "censor"); + CHECK(fun); + + JS::RootedValue filter(cx, JS::ObjectValue(*JS_GetFunctionObject(fun))); + + CHECK(TryParse(cx, "true", filter)); + CHECK(TryParse(cx, "false", filter)); + CHECK(TryParse(cx, "null", filter)); + CHECK(TryParse(cx, "1", filter)); + CHECK(TryParse(cx, "1.75", filter)); + CHECK(TryParse(cx, "[]", filter)); + CHECK(TryParse(cx, "[1]", filter)); + CHECK(TryParse(cx, "{}", filter)); + return true; +} + +template <size_t N> +inline bool TryParse(JSContext* cx, const char (&input)[N], + JS::HandleValue filter) { + AutoInflatedString str(cx); + JS::RootedValue v(cx); + str = input; + CHECK(JS_ParseJSONWithReviver(cx, str.chars(), str.length(), filter, &v)); + CHECK(v.isNull()); + return true; +} +END_TEST(testParseJSON_reviver) |