|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 */ |
|
4 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
5 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
7 |
|
8 #include <limits> |
|
9 |
|
10 #include "jsstr.h" |
|
11 |
|
12 #include "jsapi-tests/tests.h" |
|
13 |
|
14 using namespace js; |
|
15 |
|
16 class AutoInflatedString { |
|
17 JSContext * const cx; |
|
18 jschar *chars_; |
|
19 size_t length_; |
|
20 |
|
21 public: |
|
22 AutoInflatedString(JSContext *cx) : cx(cx), chars_(nullptr), length_(0) { } |
|
23 ~AutoInflatedString() { |
|
24 JS_free(cx, chars_); |
|
25 } |
|
26 |
|
27 template<size_t N> void operator=(const char (&str)[N]) { |
|
28 length_ = N - 1; |
|
29 chars_ = InflateString(cx, str, &length_); |
|
30 if (!chars_) |
|
31 abort(); |
|
32 } |
|
33 |
|
34 const jschar *chars() const { return chars_; } |
|
35 size_t length() const { return length_; } |
|
36 }; |
|
37 |
|
38 BEGIN_TEST(testParseJSON_success) |
|
39 { |
|
40 // Primitives |
|
41 JS::RootedValue expected(cx); |
|
42 expected = JSVAL_TRUE; |
|
43 CHECK(TryParse(cx, "true", expected)); |
|
44 |
|
45 expected = JSVAL_FALSE; |
|
46 CHECK(TryParse(cx, "false", expected)); |
|
47 |
|
48 expected = JSVAL_NULL; |
|
49 CHECK(TryParse(cx, "null", expected)); |
|
50 |
|
51 expected = INT_TO_JSVAL(0); |
|
52 CHECK(TryParse(cx, "0", expected)); |
|
53 |
|
54 expected = INT_TO_JSVAL(1); |
|
55 CHECK(TryParse(cx, "1", expected)); |
|
56 |
|
57 expected = INT_TO_JSVAL(-1); |
|
58 CHECK(TryParse(cx, "-1", expected)); |
|
59 |
|
60 expected = DOUBLE_TO_JSVAL(1); |
|
61 CHECK(TryParse(cx, "1", expected)); |
|
62 |
|
63 expected = DOUBLE_TO_JSVAL(1.75); |
|
64 CHECK(TryParse(cx, "1.75", expected)); |
|
65 |
|
66 expected = DOUBLE_TO_JSVAL(9e9); |
|
67 CHECK(TryParse(cx, "9e9", expected)); |
|
68 |
|
69 expected = DOUBLE_TO_JSVAL(std::numeric_limits<double>::infinity()); |
|
70 CHECK(TryParse(cx, "9e99999", expected)); |
|
71 |
|
72 JS::Rooted<JSFlatString*> str(cx); |
|
73 |
|
74 const jschar emptystr[] = { '\0' }; |
|
75 str = js_NewStringCopyN<CanGC>(cx, emptystr, 0); |
|
76 CHECK(str); |
|
77 expected = STRING_TO_JSVAL(str); |
|
78 CHECK(TryParse(cx, "\"\"", expected)); |
|
79 |
|
80 const jschar nullstr[] = { '\0' }; |
|
81 str = NewString(cx, nullstr); |
|
82 CHECK(str); |
|
83 expected = STRING_TO_JSVAL(str); |
|
84 CHECK(TryParse(cx, "\"\\u0000\"", expected)); |
|
85 |
|
86 const jschar backstr[] = { '\b' }; |
|
87 str = NewString(cx, backstr); |
|
88 CHECK(str); |
|
89 expected = STRING_TO_JSVAL(str); |
|
90 CHECK(TryParse(cx, "\"\\b\"", expected)); |
|
91 CHECK(TryParse(cx, "\"\\u0008\"", expected)); |
|
92 |
|
93 const jschar newlinestr[] = { '\n', }; |
|
94 str = NewString(cx, newlinestr); |
|
95 CHECK(str); |
|
96 expected = STRING_TO_JSVAL(str); |
|
97 CHECK(TryParse(cx, "\"\\n\"", expected)); |
|
98 CHECK(TryParse(cx, "\"\\u000A\"", expected)); |
|
99 |
|
100 |
|
101 // Arrays |
|
102 JS::RootedValue v(cx), v2(cx); |
|
103 JS::RootedObject obj(cx); |
|
104 |
|
105 CHECK(Parse(cx, "[]", &v)); |
|
106 CHECK(!JSVAL_IS_PRIMITIVE(v)); |
|
107 obj = JSVAL_TO_OBJECT(v); |
|
108 CHECK(JS_IsArrayObject(cx, obj)); |
|
109 CHECK(JS_GetProperty(cx, obj, "length", &v2)); |
|
110 CHECK_SAME(v2, JSVAL_ZERO); |
|
111 |
|
112 CHECK(Parse(cx, "[1]", &v)); |
|
113 CHECK(!JSVAL_IS_PRIMITIVE(v)); |
|
114 obj = JSVAL_TO_OBJECT(v); |
|
115 CHECK(JS_IsArrayObject(cx, obj)); |
|
116 CHECK(JS_GetProperty(cx, obj, "0", &v2)); |
|
117 CHECK_SAME(v2, JSVAL_ONE); |
|
118 CHECK(JS_GetProperty(cx, obj, "length", &v2)); |
|
119 CHECK_SAME(v2, JSVAL_ONE); |
|
120 |
|
121 |
|
122 // Objects |
|
123 CHECK(Parse(cx, "{}", &v)); |
|
124 CHECK(!JSVAL_IS_PRIMITIVE(v)); |
|
125 obj = JSVAL_TO_OBJECT(v); |
|
126 CHECK(!JS_IsArrayObject(cx, obj)); |
|
127 |
|
128 CHECK(Parse(cx, "{ \"f\": 17 }", &v)); |
|
129 CHECK(!JSVAL_IS_PRIMITIVE(v)); |
|
130 obj = JSVAL_TO_OBJECT(v); |
|
131 CHECK(!JS_IsArrayObject(cx, obj)); |
|
132 CHECK(JS_GetProperty(cx, obj, "f", &v2)); |
|
133 CHECK_SAME(v2, INT_TO_JSVAL(17)); |
|
134 |
|
135 return true; |
|
136 } |
|
137 |
|
138 template<size_t N> static JSFlatString * |
|
139 NewString(JSContext *cx, const jschar (&chars)[N]) |
|
140 { |
|
141 return js_NewStringCopyN<CanGC>(cx, chars, N); |
|
142 } |
|
143 |
|
144 template<size_t N> inline bool |
|
145 Parse(JSContext *cx, const char (&input)[N], JS::MutableHandleValue vp) |
|
146 { |
|
147 AutoInflatedString str(cx); |
|
148 str = input; |
|
149 CHECK(JS_ParseJSON(cx, str.chars(), str.length(), vp)); |
|
150 return true; |
|
151 } |
|
152 |
|
153 template<size_t N> inline bool |
|
154 TryParse(JSContext *cx, const char (&input)[N], JS::HandleValue expected) |
|
155 { |
|
156 AutoInflatedString str(cx); |
|
157 RootedValue v(cx); |
|
158 str = input; |
|
159 CHECK(JS_ParseJSON(cx, str.chars(), str.length(), &v)); |
|
160 CHECK_SAME(v, expected); |
|
161 return true; |
|
162 } |
|
163 END_TEST(testParseJSON_success) |
|
164 |
|
165 BEGIN_TEST(testParseJSON_error) |
|
166 { |
|
167 CHECK(Error(cx, "" , "1", "1")); |
|
168 CHECK(Error(cx, "\n" , "2", "1")); |
|
169 CHECK(Error(cx, "\r" , "2", "1")); |
|
170 CHECK(Error(cx, "\r\n" , "2", "1")); |
|
171 |
|
172 CHECK(Error(cx, "[" , "1", "2")); |
|
173 CHECK(Error(cx, "[,]" , "1", "2")); |
|
174 CHECK(Error(cx, "[1,]" , "1", "4")); |
|
175 CHECK(Error(cx, "{a:2}" , "1", "2")); |
|
176 CHECK(Error(cx, "{\"a\":2,}" , "1", "8")); |
|
177 CHECK(Error(cx, "]" , "1", "1")); |
|
178 CHECK(Error(cx, "\"" , "1", "2")); |
|
179 CHECK(Error(cx, "{]" , "1", "2")); |
|
180 CHECK(Error(cx, "[}" , "1", "2")); |
|
181 CHECK(Error(cx, "'wrongly-quoted string'" , "1", "1")); |
|
182 |
|
183 CHECK(Error(cx, "{\"a\":2 \n b:3}" , "2", "2")); |
|
184 CHECK(Error(cx, "\n[" , "2", "2")); |
|
185 CHECK(Error(cx, "\n[,]" , "2", "2")); |
|
186 CHECK(Error(cx, "\n[1,]" , "2", "4")); |
|
187 CHECK(Error(cx, "\n{a:2}" , "2", "2")); |
|
188 CHECK(Error(cx, "\n{\"a\":2,}" , "2", "8")); |
|
189 CHECK(Error(cx, "\n]" , "2", "1")); |
|
190 CHECK(Error(cx, "\"bad string\n\"" , "1", "12")); |
|
191 CHECK(Error(cx, "\r'wrongly-quoted string'" , "2", "1")); |
|
192 CHECK(Error(cx, "\n\"" , "2", "2")); |
|
193 CHECK(Error(cx, "\n{]" , "2", "2")); |
|
194 CHECK(Error(cx, "\n[}" , "2", "2")); |
|
195 CHECK(Error(cx, "{\"a\":[2,3],\n\"b\":,5,6}" , "2", "5")); |
|
196 |
|
197 CHECK(Error(cx, "{\"a\":2 \r b:3}" , "2", "2")); |
|
198 CHECK(Error(cx, "\r[" , "2", "2")); |
|
199 CHECK(Error(cx, "\r[,]" , "2", "2")); |
|
200 CHECK(Error(cx, "\r[1,]" , "2", "4")); |
|
201 CHECK(Error(cx, "\r{a:2}" , "2", "2")); |
|
202 CHECK(Error(cx, "\r{\"a\":2,}" , "2", "8")); |
|
203 CHECK(Error(cx, "\r]" , "2", "1")); |
|
204 CHECK(Error(cx, "\"bad string\r\"" , "1", "12")); |
|
205 CHECK(Error(cx, "\r'wrongly-quoted string'" , "2", "1")); |
|
206 CHECK(Error(cx, "\r\"" , "2", "2")); |
|
207 CHECK(Error(cx, "\r{]" , "2", "2")); |
|
208 CHECK(Error(cx, "\r[}" , "2", "2")); |
|
209 CHECK(Error(cx, "{\"a\":[2,3],\r\"b\":,5,6}" , "2", "5")); |
|
210 |
|
211 CHECK(Error(cx, "{\"a\":2 \r\n b:3}" , "2", "2")); |
|
212 CHECK(Error(cx, "\r\n[" , "2", "2")); |
|
213 CHECK(Error(cx, "\r\n[,]" , "2", "2")); |
|
214 CHECK(Error(cx, "\r\n[1,]" , "2", "4")); |
|
215 CHECK(Error(cx, "\r\n{a:2}" , "2", "2")); |
|
216 CHECK(Error(cx, "\r\n{\"a\":2,}" , "2", "8")); |
|
217 CHECK(Error(cx, "\r\n]" , "2", "1")); |
|
218 CHECK(Error(cx, "\"bad string\r\n\"" , "1", "12")); |
|
219 CHECK(Error(cx, "\r\n'wrongly-quoted string'" , "2", "1")); |
|
220 CHECK(Error(cx, "\r\n\"" , "2", "2")); |
|
221 CHECK(Error(cx, "\r\n{]" , "2", "2")); |
|
222 CHECK(Error(cx, "\r\n[}" , "2", "2")); |
|
223 CHECK(Error(cx, "{\"a\":[2,3],\r\n\"b\":,5,6}" , "2", "5")); |
|
224 |
|
225 CHECK(Error(cx, "\n\"bad string\n\"" , "2", "12")); |
|
226 CHECK(Error(cx, "\r\"bad string\r\"" , "2", "12")); |
|
227 CHECK(Error(cx, "\r\n\"bad string\r\n\"" , "2", "12")); |
|
228 |
|
229 CHECK(Error(cx, "{\n\"a\":[2,3],\r\"b\":,5,6}" , "3", "5")); |
|
230 CHECK(Error(cx, "{\r\"a\":[2,3],\n\"b\":,5,6}" , "3", "5")); |
|
231 CHECK(Error(cx, "[\"\\t\\q" , "1", "6")); |
|
232 CHECK(Error(cx, "[\"\\t\x00" , "1", "5")); |
|
233 CHECK(Error(cx, "[\"\\t\x01" , "1", "5")); |
|
234 CHECK(Error(cx, "[\"\\t\\\x00" , "1", "6")); |
|
235 CHECK(Error(cx, "[\"\\t\\\x01" , "1", "6")); |
|
236 |
|
237 // Unicode escape errors are messy. The first bad character could be |
|
238 // non-hexadecimal, or it could be absent entirely. Include tests where |
|
239 // there's a bad character, followed by zero to as many characters as are |
|
240 // needed to form a complete Unicode escape sequence, plus one. (The extra |
|
241 // characters beyond are valuable because our implementation checks for |
|
242 // too-few subsequent characters first, before checking for subsequent |
|
243 // non-hexadecimal characters. So \u<END>, \u0<END>, \u00<END>, and |
|
244 // \u000<END> are all *detected* as invalid by the same code path, but the |
|
245 // process of computing the first invalid character follows a different |
|
246 // code path for each. And \uQQQQ, \u0QQQ, \u00QQ, and \u000Q are detected |
|
247 // as invalid by the same code path [ignoring which precise subexpression |
|
248 // triggers failure of a single condition], but the computation of the |
|
249 // first invalid character follows a different code path for each.) |
|
250 CHECK(Error(cx, "[\"\\t\\u" , "1", "7")); |
|
251 CHECK(Error(cx, "[\"\\t\\uZ" , "1", "7")); |
|
252 CHECK(Error(cx, "[\"\\t\\uZZ" , "1", "7")); |
|
253 CHECK(Error(cx, "[\"\\t\\uZZZ" , "1", "7")); |
|
254 CHECK(Error(cx, "[\"\\t\\uZZZZ" , "1", "7")); |
|
255 CHECK(Error(cx, "[\"\\t\\uZZZZZ" , "1", "7")); |
|
256 |
|
257 CHECK(Error(cx, "[\"\\t\\u0" , "1", "8")); |
|
258 CHECK(Error(cx, "[\"\\t\\u0Z" , "1", "8")); |
|
259 CHECK(Error(cx, "[\"\\t\\u0ZZ" , "1", "8")); |
|
260 CHECK(Error(cx, "[\"\\t\\u0ZZZ" , "1", "8")); |
|
261 CHECK(Error(cx, "[\"\\t\\u0ZZZZ" , "1", "8")); |
|
262 |
|
263 CHECK(Error(cx, "[\"\\t\\u00" , "1", "9")); |
|
264 CHECK(Error(cx, "[\"\\t\\u00Z" , "1", "9")); |
|
265 CHECK(Error(cx, "[\"\\t\\u00ZZ" , "1", "9")); |
|
266 CHECK(Error(cx, "[\"\\t\\u00ZZZ" , "1", "9")); |
|
267 |
|
268 CHECK(Error(cx, "[\"\\t\\u000" , "1", "10")); |
|
269 CHECK(Error(cx, "[\"\\t\\u000Z" , "1", "10")); |
|
270 CHECK(Error(cx, "[\"\\t\\u000ZZ" , "1", "10")); |
|
271 |
|
272 return true; |
|
273 } |
|
274 |
|
275 template<size_t N, size_t M, size_t L> inline bool |
|
276 Error(JSContext *cx, const char (&input)[N], const char (&expectedLine)[M], |
|
277 const char (&expectedColumn)[L]) |
|
278 { |
|
279 AutoInflatedString str(cx), line(cx), column(cx); |
|
280 RootedValue dummy(cx); |
|
281 str = input; |
|
282 |
|
283 ContextPrivate p = {0, 0}; |
|
284 CHECK(!JS_GetContextPrivate(cx)); |
|
285 JS_SetContextPrivate(cx, &p); |
|
286 JSErrorReporter old = JS_SetErrorReporter(cx, ReportJSONError); |
|
287 bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy); |
|
288 JS_SetErrorReporter(cx, old); |
|
289 JS_SetContextPrivate(cx, nullptr); |
|
290 |
|
291 CHECK(!ok); |
|
292 CHECK(!p.unexpectedErrorCount); |
|
293 CHECK(p.expectedErrorCount == 1); |
|
294 column = expectedColumn; |
|
295 CHECK(js_strcmp(column.chars(), p.column) == 0); |
|
296 line = expectedLine; |
|
297 CHECK(js_strcmp(line.chars(), p.line) == 0); |
|
298 |
|
299 /* We do not execute JS, so there should be no exception thrown. */ |
|
300 CHECK(!JS_IsExceptionPending(cx)); |
|
301 |
|
302 return true; |
|
303 } |
|
304 |
|
305 struct ContextPrivate { |
|
306 static const size_t MaxSize = sizeof("4294967295"); |
|
307 unsigned unexpectedErrorCount; |
|
308 unsigned expectedErrorCount; |
|
309 jschar column[MaxSize]; |
|
310 jschar line[MaxSize]; |
|
311 }; |
|
312 |
|
313 static void |
|
314 ReportJSONError(JSContext *cx, const char *message, JSErrorReport *report) |
|
315 { |
|
316 ContextPrivate *p = static_cast<ContextPrivate *>(JS_GetContextPrivate(cx)); |
|
317 // Although messageArgs[1] and messageArgs[2] are jschar*, we cast them to char* |
|
318 // here because JSONParser::error() stores char* strings in them. |
|
319 js_strncpy(p->line, report->messageArgs[1], js_strlen(report->messageArgs[1])); |
|
320 js_strncpy(p->column, report->messageArgs[2], js_strlen(report->messageArgs[2])); |
|
321 if (report->errorNumber == JSMSG_JSON_BAD_PARSE) |
|
322 p->expectedErrorCount++; |
|
323 else |
|
324 p->unexpectedErrorCount++; |
|
325 } |
|
326 |
|
327 END_TEST(testParseJSON_error) |
|
328 |
|
329 static bool |
|
330 Censor(JSContext *cx, unsigned argc, jsval *vp) |
|
331 { |
|
332 JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
|
333 JS_ASSERT(args.length() == 2); |
|
334 #ifdef DEBUG |
|
335 JS_ASSERT(args[0].isString()); |
|
336 #endif |
|
337 args.rval().setNull(); |
|
338 return true; |
|
339 } |
|
340 |
|
341 BEGIN_TEST(testParseJSON_reviver) |
|
342 { |
|
343 JSFunction *fun = JS_NewFunction(cx, Censor, 0, 0, global, "censor"); |
|
344 CHECK(fun); |
|
345 |
|
346 JS::RootedValue filter(cx, OBJECT_TO_JSVAL(JS_GetFunctionObject(fun))); |
|
347 |
|
348 CHECK(TryParse(cx, "true", filter)); |
|
349 CHECK(TryParse(cx, "false", filter)); |
|
350 CHECK(TryParse(cx, "null", filter)); |
|
351 CHECK(TryParse(cx, "1", filter)); |
|
352 CHECK(TryParse(cx, "1.75", filter)); |
|
353 CHECK(TryParse(cx, "[]", filter)); |
|
354 CHECK(TryParse(cx, "[1]", filter)); |
|
355 CHECK(TryParse(cx, "{}", filter)); |
|
356 return true; |
|
357 } |
|
358 |
|
359 template<size_t N> inline bool |
|
360 TryParse(JSContext *cx, const char (&input)[N], JS::HandleValue filter) |
|
361 { |
|
362 AutoInflatedString str(cx); |
|
363 JS::RootedValue v(cx); |
|
364 str = input; |
|
365 CHECK(JS_ParseJSONWithReviver(cx, str.chars(), str.length(), filter, &v)); |
|
366 CHECK_SAME(v, JSVAL_NULL); |
|
367 return true; |
|
368 } |
|
369 END_TEST(testParseJSON_reviver) |