mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-27 02:29:35 +00:00
374 lines
14 KiB
C++
374 lines
14 KiB
C++
/* -*- 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 "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();
|
|
}
|
|
|
|
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, size_t M, size_t L> inline bool
|
|
Error(JSContext* cx, const char (&input)[N], const char (&expectedLine)[M],
|
|
const char (&expectedColumn)[L])
|
|
{
|
|
AutoInflatedString str(cx), line(cx), column(cx);
|
|
RootedValue dummy(cx);
|
|
str = input;
|
|
|
|
ContextPrivate p = {0, 0};
|
|
CHECK(!JS_GetContextPrivate(cx));
|
|
JS_SetContextPrivate(cx, &p);
|
|
JSErrorReporter old = JS_SetErrorReporter(rt, ReportJSONError);
|
|
bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy);
|
|
JS_SetErrorReporter(rt, old);
|
|
JS_SetContextPrivate(cx, nullptr);
|
|
|
|
CHECK(!ok);
|
|
CHECK(!p.unexpectedErrorCount);
|
|
CHECK(p.expectedErrorCount == 1);
|
|
column = expectedColumn;
|
|
CHECK(js_strcmp(column.chars(), p.column) == 0);
|
|
line = expectedLine;
|
|
CHECK(js_strcmp(line.chars(), p.line) == 0);
|
|
|
|
/* We do not execute JS, so there should be no exception thrown. */
|
|
CHECK(!JS_IsExceptionPending(cx));
|
|
|
|
return true;
|
|
}
|
|
|
|
struct ContextPrivate {
|
|
static const size_t MaxSize = sizeof("4294967295");
|
|
unsigned unexpectedErrorCount;
|
|
unsigned expectedErrorCount;
|
|
char16_t column[MaxSize];
|
|
char16_t line[MaxSize];
|
|
};
|
|
|
|
static void
|
|
ReportJSONError(JSContext* cx, const char* message, JSErrorReport* report)
|
|
{
|
|
ContextPrivate* p = static_cast<ContextPrivate*>(JS_GetContextPrivate(cx));
|
|
// Although messageArgs[1] and messageArgs[2] are char16_t*, we cast them to char*
|
|
// here because JSONParser::error() stores char* strings in them.
|
|
js_strncpy(p->line, report->messageArgs[1], js_strlen(report->messageArgs[1]));
|
|
js_strncpy(p->column, report->messageArgs[2], js_strlen(report->messageArgs[2]));
|
|
if (report->errorNumber == JSMSG_JSON_BAD_PARSE)
|
|
p->expectedErrorCount++;
|
|
else
|
|
p->unexpectedErrorCount++;
|
|
}
|
|
|
|
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)
|