/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: */ /* 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 "jsprf.h" #include "jsstr.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<JSFlatString*> 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 JSFlatString* 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); RootedValue exn(cx); CHECK(JS_GetPendingException(cx, &exn)); JS_ClearPendingException(cx); js::ErrorReport report(cx); CHECK(report.init(cx, exn, js::ErrorReport::WithSideEffects)); CHECK(report.report()->errorNumber == JSMSG_JSON_BAD_PARSE); const char* lineAndColumnASCII = JS_smprintf("line %d column %d", expectedLine, expectedColumn); CHECK(strstr(report.toStringResult().c_str(), lineAndColumnASCII) != nullptr); js_free((void*)lineAndColumnASCII); /* 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)