summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testParseJSON.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsapi-tests/testParseJSON.cpp')
-rw-r--r--js/src/jsapi-tests/testParseJSON.cpp358
1 files changed, 358 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testParseJSON.cpp b/js/src/jsapi-tests/testParseJSON.cpp
new file mode 100644
index 000000000..e82c67911
--- /dev/null
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -0,0 +1,358 @@
+/* -*- 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)