/work/obj-fuzz/dist/include/mozilla/JSONWriter.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | /* A JSON pretty-printer class. */ |
8 | | |
9 | | // A typical JSON-writing library requires you to first build up a data |
10 | | // structure that represents a JSON object and then serialize it (to file, or |
11 | | // somewhere else). This approach makes for a clean API, but building the data |
12 | | // structure takes up memory. Sometimes that isn't desirable, such as when the |
13 | | // JSON data is produced for memory reporting. |
14 | | // |
15 | | // The JSONWriter class instead allows JSON data to be written out |
16 | | // incrementally without building up large data structures. |
17 | | // |
18 | | // The API is slightly uglier than you would see in a typical JSON-writing |
19 | | // library, but still fairly easy to use. It's possible to generate invalid |
20 | | // JSON with JSONWriter, but typically the most basic testing will identify any |
21 | | // such problems. |
22 | | // |
23 | | // Similarly, there are no RAII facilities for automatically closing objects |
24 | | // and arrays. These would be nice if you are generating all your code within |
25 | | // nested functions, but in other cases you'd have to maintain an explicit |
26 | | // stack of RAII objects and manually unwind it, which is no better than just |
27 | | // calling "end" functions. Furthermore, the consequences of forgetting to |
28 | | // close an object or array are obvious and, again, will be identified via |
29 | | // basic testing, unlike other cases where RAII is typically used (e.g. smart |
30 | | // pointers) and the consequences of defects are more subtle. |
31 | | // |
32 | | // Importantly, the class does solve the two hard problems of JSON |
33 | | // pretty-printing, which are (a) correctly escaping strings, and (b) adding |
34 | | // appropriate indentation and commas between items. |
35 | | // |
36 | | // By default, every property is placed on its own line. However, it is |
37 | | // possible to request that objects and arrays be placed entirely on a single |
38 | | // line, which can reduce output size significantly in some cases. |
39 | | // |
40 | | // Strings used (for property names and string property values) are |const |
41 | | // char*| throughout, and can be ASCII or UTF-8. |
42 | | // |
43 | | // EXAMPLE |
44 | | // ------- |
45 | | // Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The |
46 | | // following code: |
47 | | // |
48 | | // JSONWriter w(MakeUnique<MyWriteFunc>()); |
49 | | // w.Start(); |
50 | | // { |
51 | | // w.NullProperty("null"); |
52 | | // w.BoolProperty("bool", true); |
53 | | // w.IntProperty("int", 1); |
54 | | // w.StartArrayProperty("array"); |
55 | | // { |
56 | | // w.StringElement("string"); |
57 | | // w.StartObjectElement(); |
58 | | // { |
59 | | // w.DoubleProperty("double", 3.4); |
60 | | // w.StartArrayProperty("single-line array", w.SingleLineStyle); |
61 | | // { |
62 | | // w.IntElement(1); |
63 | | // w.StartObjectElement(); // SingleLineStyle is inherited from |
64 | | // w.EndObjectElement(); // above for this collection |
65 | | // } |
66 | | // w.EndArray(); |
67 | | // } |
68 | | // w.EndObjectElement(); |
69 | | // } |
70 | | // w.EndArrayProperty(); |
71 | | // } |
72 | | // w.End(); |
73 | | // |
74 | | // will produce pretty-printed output for the following JSON object: |
75 | | // |
76 | | // { |
77 | | // "null": null, |
78 | | // "bool": true, |
79 | | // "int": 1, |
80 | | // "array": [ |
81 | | // "string", |
82 | | // { |
83 | | // "double": 3.4, |
84 | | // "single-line array": [1, {}] |
85 | | // } |
86 | | // ] |
87 | | // } |
88 | | // |
89 | | // The nesting in the example code is obviously optional, but can aid |
90 | | // readability. |
91 | | |
92 | | #ifndef mozilla_JSONWriter_h |
93 | | #define mozilla_JSONWriter_h |
94 | | |
95 | | #include "double-conversion/double-conversion.h" |
96 | | #include "mozilla/Assertions.h" |
97 | | #include "mozilla/IntegerPrintfMacros.h" |
98 | | #include "mozilla/PodOperations.h" |
99 | | #include "mozilla/Sprintf.h" |
100 | | #include "mozilla/UniquePtr.h" |
101 | | #include "mozilla/Vector.h" |
102 | | |
103 | | #include <stdio.h> |
104 | | |
105 | | namespace mozilla { |
106 | | |
107 | | // A quasi-functor for JSONWriter. We don't use a true functor because that |
108 | | // requires templatizing JSONWriter, and the templatization seeps to lots of |
109 | | // places we don't want it to. |
110 | | class JSONWriteFunc |
111 | | { |
112 | | public: |
113 | | virtual void Write(const char* aStr) = 0; |
114 | 0 | virtual ~JSONWriteFunc() {} |
115 | | }; |
116 | | |
117 | | // Ideally this would be within |EscapedString| but when compiling with GCC |
118 | | // on Linux that caused link errors, whereas this formulation didn't. |
119 | | namespace detail { |
120 | | extern MFBT_DATA const char gTwoCharEscapes[256]; |
121 | | } // namespace detail |
122 | | |
123 | | class JSONWriter |
124 | | { |
125 | | // From http://www.ietf.org/rfc/rfc4627.txt: |
126 | | // |
127 | | // "All Unicode characters may be placed within the quotation marks except |
128 | | // for the characters that must be escaped: quotation mark, reverse |
129 | | // solidus, and the control characters (U+0000 through U+001F)." |
130 | | // |
131 | | // This implementation uses two-char escape sequences where possible, namely: |
132 | | // |
133 | | // \", \\, \b, \f, \n, \r, \t |
134 | | // |
135 | | // All control characters not in the above list are represented with a |
136 | | // six-char escape sequence, e.g. '\u000b' (a.k.a. '\v'). |
137 | | // |
138 | | class EscapedString |
139 | | { |
140 | | // Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned| |
141 | | // indicates which one is in use. They're not within a union because that |
142 | | // wouldn't work with UniquePtr. |
143 | | bool mIsOwned; |
144 | | const char* mUnownedStr; |
145 | | UniquePtr<char[]> mOwnedStr; |
146 | | |
147 | | void SanityCheck() const |
148 | 0 | { |
149 | 0 | MOZ_ASSERT_IF( mIsOwned, mOwnedStr.get() && !mUnownedStr); |
150 | 0 | MOZ_ASSERT_IF(!mIsOwned, !mOwnedStr.get() && mUnownedStr); |
151 | 0 | } Unexecuted instantiation: mozilla::JSONWriter::EscapedString::SanityCheck() const Unexecuted instantiation: mozilla::JSONWriter::EscapedString::SanityCheck() const |
152 | | |
153 | | static char hexDigitToAsciiChar(uint8_t u) |
154 | 0 | { |
155 | 0 | u = u & 0xf; |
156 | 0 | return u < 10 ? '0' + u : 'a' + (u - 10); |
157 | 0 | } |
158 | | |
159 | | public: |
160 | | explicit EscapedString(const char* aStr) |
161 | | : mUnownedStr(nullptr) |
162 | | , mOwnedStr(nullptr) |
163 | 0 | { |
164 | 0 | const char* p; |
165 | 0 |
|
166 | 0 | // First, see if we need to modify the string. |
167 | 0 | size_t nExtra = 0; |
168 | 0 | p = aStr; |
169 | 0 | while (true) { |
170 | 0 | uint8_t u = *p; // ensure it can't be interpreted as negative |
171 | 0 | if (u == 0) { |
172 | 0 | break; |
173 | 0 | } |
174 | 0 | if (detail::gTwoCharEscapes[u]) { |
175 | 0 | nExtra += 1; |
176 | 0 | } else if (u <= 0x1f) { |
177 | 0 | nExtra += 5; |
178 | 0 | } |
179 | 0 | p++; |
180 | 0 | } |
181 | 0 |
|
182 | 0 | if (nExtra == 0) { |
183 | 0 | // No escapes needed. Easy. |
184 | 0 | mIsOwned = false; |
185 | 0 | mUnownedStr = aStr; |
186 | 0 | return; |
187 | 0 | } |
188 | 0 |
|
189 | 0 | // Escapes are needed. We'll create a new string. |
190 | 0 | mIsOwned = true; |
191 | 0 | size_t len = (p - aStr) + nExtra; |
192 | 0 | mOwnedStr = MakeUnique<char[]>(len + 1); |
193 | 0 |
|
194 | 0 | p = aStr; |
195 | 0 | size_t i = 0; |
196 | 0 |
|
197 | 0 | while (true) { |
198 | 0 | uint8_t u = *p; // ensure it can't be interpreted as negative |
199 | 0 | if (u == 0) { |
200 | 0 | mOwnedStr[i] = 0; |
201 | 0 | break; |
202 | 0 | } |
203 | 0 | if (detail::gTwoCharEscapes[u]) { |
204 | 0 | mOwnedStr[i++] = '\\'; |
205 | 0 | mOwnedStr[i++] = detail::gTwoCharEscapes[u]; |
206 | 0 | } else if (u <= 0x1f) { |
207 | 0 | mOwnedStr[i++] = '\\'; |
208 | 0 | mOwnedStr[i++] = 'u'; |
209 | 0 | mOwnedStr[i++] = '0'; |
210 | 0 | mOwnedStr[i++] = '0'; |
211 | 0 | mOwnedStr[i++] = hexDigitToAsciiChar((u & 0x00f0) >> 4); |
212 | 0 | mOwnedStr[i++] = hexDigitToAsciiChar(u & 0x000f); |
213 | 0 | } else { |
214 | 0 | mOwnedStr[i++] = u; |
215 | 0 | } |
216 | 0 | p++; |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | | ~EscapedString() |
221 | 0 | { |
222 | 0 | SanityCheck(); |
223 | 0 | } |
224 | | |
225 | | const char* get() const |
226 | 0 | { |
227 | 0 | SanityCheck(); |
228 | 0 | return mIsOwned ? mOwnedStr.get() : mUnownedStr; |
229 | 0 | } |
230 | | }; |
231 | | |
232 | | public: |
233 | | // Collections (objects and arrays) are printed in a multi-line style by |
234 | | // default. This can be changed to a single-line style if SingleLineStyle is |
235 | | // specified. If a collection is printed in single-line style, every nested |
236 | | // collection within it is also printed in single-line style, even if |
237 | | // multi-line style is requested. |
238 | | enum CollectionStyle { |
239 | | MultiLineStyle, // the default |
240 | | SingleLineStyle |
241 | | }; |
242 | | |
243 | | protected: |
244 | | const UniquePtr<JSONWriteFunc> mWriter; |
245 | | Vector<bool, 8> mNeedComma; // do we need a comma at depth N? |
246 | | Vector<bool, 8> mNeedNewlines; // do we need newlines at depth N? |
247 | | size_t mDepth; // the current nesting depth |
248 | | |
249 | | void Indent() |
250 | 0 | { |
251 | 0 | for (size_t i = 0; i < mDepth; i++) { |
252 | 0 | mWriter->Write(" "); |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | | // Adds whatever is necessary (maybe a comma, and then a newline and |
257 | | // whitespace) to separate an item (property or element) from what's come |
258 | | // before. |
259 | | void Separator() |
260 | 0 | { |
261 | 0 | if (mNeedComma[mDepth]) { |
262 | 0 | mWriter->Write(","); |
263 | 0 | } |
264 | 0 | if (mDepth > 0 && mNeedNewlines[mDepth]) { |
265 | 0 | mWriter->Write("\n"); |
266 | 0 | Indent(); |
267 | 0 | } else if (mNeedComma[mDepth]) { |
268 | 0 | mWriter->Write(" "); |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | | void PropertyNameAndColon(const char* aName) |
273 | 0 | { |
274 | 0 | EscapedString escapedName(aName); |
275 | 0 | mWriter->Write("\""); |
276 | 0 | mWriter->Write(escapedName.get()); |
277 | 0 | mWriter->Write("\": "); |
278 | 0 | } |
279 | | |
280 | | void Scalar(const char* aMaybePropertyName, const char* aStringValue) |
281 | 0 | { |
282 | 0 | Separator(); |
283 | 0 | if (aMaybePropertyName) { |
284 | 0 | PropertyNameAndColon(aMaybePropertyName); |
285 | 0 | } |
286 | 0 | mWriter->Write(aStringValue); |
287 | 0 | mNeedComma[mDepth] = true; |
288 | 0 | } |
289 | | |
290 | | void QuotedScalar(const char* aMaybePropertyName, const char* aStringValue) |
291 | 0 | { |
292 | 0 | Separator(); |
293 | 0 | if (aMaybePropertyName) { |
294 | 0 | PropertyNameAndColon(aMaybePropertyName); |
295 | 0 | } |
296 | 0 | mWriter->Write("\""); |
297 | 0 | mWriter->Write(aStringValue); |
298 | 0 | mWriter->Write("\""); |
299 | 0 | mNeedComma[mDepth] = true; |
300 | 0 | } |
301 | | |
302 | | void NewVectorEntries() |
303 | 0 | { |
304 | 0 | // If these tiny allocations OOM we might as well just crash because we |
305 | 0 | // must be in serious memory trouble. |
306 | 0 | MOZ_RELEASE_ASSERT(mNeedComma.resizeUninitialized(mDepth + 1)); |
307 | 0 | MOZ_RELEASE_ASSERT(mNeedNewlines.resizeUninitialized(mDepth + 1)); |
308 | 0 | mNeedComma[mDepth] = false; |
309 | 0 | mNeedNewlines[mDepth] = true; |
310 | 0 | } Unexecuted instantiation: mozilla::JSONWriter::NewVectorEntries() Unexecuted instantiation: mozilla::JSONWriter::NewVectorEntries() |
311 | | |
312 | | void StartCollection(const char* aMaybePropertyName, const char* aStartChar, |
313 | | CollectionStyle aStyle = MultiLineStyle) |
314 | 0 | { |
315 | 0 | Separator(); |
316 | 0 | if (aMaybePropertyName) { |
317 | 0 | PropertyNameAndColon(aMaybePropertyName); |
318 | 0 | } |
319 | 0 | mWriter->Write(aStartChar); |
320 | 0 | mNeedComma[mDepth] = true; |
321 | 0 | mDepth++; |
322 | 0 | NewVectorEntries(); |
323 | 0 | mNeedNewlines[mDepth] = |
324 | 0 | mNeedNewlines[mDepth - 1] && aStyle == MultiLineStyle; |
325 | 0 | } |
326 | | |
327 | | // Adds the whitespace and closing char necessary to end a collection. |
328 | | void EndCollection(const char* aEndChar) |
329 | 0 | { |
330 | 0 | MOZ_ASSERT(mDepth > 0); |
331 | 0 | if (mNeedNewlines[mDepth]) { |
332 | 0 | mWriter->Write("\n"); |
333 | 0 | mDepth--; |
334 | 0 | Indent(); |
335 | 0 | } else { |
336 | 0 | mDepth--; |
337 | 0 | } |
338 | 0 | mWriter->Write(aEndChar); |
339 | 0 | } Unexecuted instantiation: mozilla::JSONWriter::EndCollection(char const*) Unexecuted instantiation: mozilla::JSONWriter::EndCollection(char const*) |
340 | | |
341 | | public: |
342 | | explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter) |
343 | | : mWriter(std::move(aWriter)) |
344 | | , mNeedComma() |
345 | | , mNeedNewlines() |
346 | | , mDepth(0) |
347 | 0 | { |
348 | 0 | NewVectorEntries(); |
349 | 0 | } |
350 | | |
351 | | // Returns the JSONWriteFunc passed in at creation, for temporary use. The |
352 | | // JSONWriter object still owns the JSONWriteFunc. |
353 | 0 | JSONWriteFunc* WriteFunc() const { return mWriter.get(); } |
354 | | |
355 | | // For all the following functions, the "Prints:" comment indicates what the |
356 | | // basic output looks like. However, it doesn't indicate the whitespace and |
357 | | // trailing commas, which are automatically added as required. |
358 | | // |
359 | | // All property names and string properties are escaped as necessary. |
360 | | |
361 | | // Prints: { |
362 | | void Start(CollectionStyle aStyle = MultiLineStyle) |
363 | 0 | { |
364 | 0 | StartCollection(nullptr, "{", aStyle); |
365 | 0 | } |
366 | | |
367 | | // Prints: } |
368 | 0 | void End() { EndCollection("}\n"); } |
369 | | |
370 | | // Prints: "<aName>": null |
371 | | void NullProperty(const char* aName) |
372 | 0 | { |
373 | 0 | Scalar(aName, "null"); |
374 | 0 | } |
375 | | |
376 | | // Prints: null |
377 | 0 | void NullElement() { NullProperty(nullptr); } |
378 | | |
379 | | // Prints: "<aName>": <aBool> |
380 | | void BoolProperty(const char* aName, bool aBool) |
381 | 0 | { |
382 | 0 | Scalar(aName, aBool ? "true" : "false"); |
383 | 0 | } |
384 | | |
385 | | // Prints: <aBool> |
386 | 0 | void BoolElement(bool aBool) { BoolProperty(nullptr, aBool); } |
387 | | |
388 | | // Prints: "<aName>": <aInt> |
389 | | void IntProperty(const char* aName, int64_t aInt) |
390 | 0 | { |
391 | 0 | char buf[64]; |
392 | 0 | SprintfLiteral(buf, "%" PRId64, aInt); |
393 | 0 | Scalar(aName, buf); |
394 | 0 | } |
395 | | |
396 | | // Prints: <aInt> |
397 | 0 | void IntElement(int64_t aInt) { IntProperty(nullptr, aInt); } |
398 | | |
399 | | // Prints: "<aName>": <aDouble> |
400 | | void DoubleProperty(const char* aName, double aDouble) |
401 | 0 | { |
402 | 0 | static const size_t buflen = 64; |
403 | 0 | char buf[buflen]; |
404 | 0 | const double_conversion::DoubleToStringConverter &converter = |
405 | 0 | double_conversion::DoubleToStringConverter::EcmaScriptConverter(); |
406 | 0 | double_conversion::StringBuilder builder(buf, buflen); |
407 | 0 | converter.ToShortest(aDouble, &builder); |
408 | 0 | Scalar(aName, builder.Finalize()); |
409 | 0 | } |
410 | | |
411 | | // Prints: <aDouble> |
412 | 0 | void DoubleElement(double aDouble) { DoubleProperty(nullptr, aDouble); } |
413 | | |
414 | | // Prints: "<aName>": "<aStr>" |
415 | | void StringProperty(const char* aName, const char* aStr) |
416 | 0 | { |
417 | 0 | EscapedString escapedStr(aStr); |
418 | 0 | QuotedScalar(aName, escapedStr.get()); |
419 | 0 | } |
420 | | |
421 | | // Prints: "<aStr>" |
422 | 0 | void StringElement(const char* aStr) { StringProperty(nullptr, aStr); } |
423 | | |
424 | | // Prints: "<aName>": [ |
425 | | void StartArrayProperty(const char* aName, |
426 | | CollectionStyle aStyle = MultiLineStyle) |
427 | 0 | { |
428 | 0 | StartCollection(aName, "[", aStyle); |
429 | 0 | } |
430 | | |
431 | | // Prints: [ |
432 | | void StartArrayElement(CollectionStyle aStyle = MultiLineStyle) |
433 | 0 | { |
434 | 0 | StartArrayProperty(nullptr, aStyle); |
435 | 0 | } |
436 | | |
437 | | // Prints: ] |
438 | 0 | void EndArray() { EndCollection("]"); } |
439 | | |
440 | | // Prints: "<aName>": { |
441 | | void StartObjectProperty(const char* aName, |
442 | | CollectionStyle aStyle = MultiLineStyle) |
443 | 0 | { |
444 | 0 | StartCollection(aName, "{", aStyle); |
445 | 0 | } |
446 | | |
447 | | // Prints: { |
448 | | void StartObjectElement(CollectionStyle aStyle = MultiLineStyle) |
449 | 0 | { |
450 | 0 | StartObjectProperty(nullptr, aStyle); |
451 | 0 | } |
452 | | |
453 | | // Prints: } |
454 | 0 | void EndObject() { EndCollection("}"); } |
455 | | }; |
456 | | |
457 | | } // namespace mozilla |
458 | | |
459 | | #endif /* mozilla_JSONWriter_h */ |
460 | | |