Bug 1642579 [wpt PR 23909] - [COOP access reporting] Preliminary WPT tests., a=testonly
[gecko.git] / mfbt / JSONWriter.h
bloba73e10c6bc874df5bd28729acae1664e10f2d9ef
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/. */
7 /* A JSON pretty-printer class. */
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.
15 // The JSONWriter class instead allows JSON data to be written out
16 // incrementally without building up large data structures.
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.
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.
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.
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.
40 // Strings used (for property names and string property values) are |const
41 // char*| throughout, and can be ASCII or UTF-8.
43 // EXAMPLE
44 // -------
45 // Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The
46 // following code:
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();
74 // will produce pretty-printed output for the following JSON object:
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 // }
89 // The nesting in the example code is obviously optional, but can aid
90 // readability.
92 #ifndef mozilla_JSONWriter_h
93 #define mozilla_JSONWriter_h
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"
103 #include <stdio.h>
105 namespace mozilla {
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 public:
112 virtual void Write(const char* aStr) = 0;
113 virtual void Write(const char* aStr, size_t aLen) = 0;
115 template <size_t LenPlusOne>
116 inline void WriteLiteral(const char (&aStr)[LenPlusOne]) {
117 Write(aStr, LenPlusOne - 1);
120 virtual ~JSONWriteFunc() = default;
123 // Ideally this would be within |EscapedString| but when compiling with GCC
124 // on Linux that caused link errors, whereas this formulation didn't.
125 namespace detail {
126 extern MFBT_DATA const char gTwoCharEscapes[256];
127 } // namespace detail
129 class JSONWriter {
130 // From http://www.ietf.org/rfc/rfc4627.txt:
132 // "All Unicode characters may be placed within the quotation marks except
133 // for the characters that must be escaped: quotation mark, reverse
134 // solidus, and the control characters (U+0000 through U+001F)."
136 // This implementation uses two-char escape sequences where possible, namely:
138 // \", \\, \b, \f, \n, \r, \t
140 // All control characters not in the above list are represented with a
141 // six-char escape sequence, e.g. '\u000b' (a.k.a. '\v').
143 class EscapedString {
144 // Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned|
145 // indicates which one is in use. They're not within a union because that
146 // wouldn't work with UniquePtr.
147 bool mIsOwned;
148 size_t mLen;
149 const char* mUnownedStr;
150 UniquePtr<char[]> mOwnedStr;
152 void SanityCheck() const {
153 MOZ_ASSERT_IF(mIsOwned, mOwnedStr.get() && !mUnownedStr);
154 MOZ_ASSERT_IF(!mIsOwned, !mOwnedStr.get() && mUnownedStr);
157 static char hexDigitToAsciiChar(uint8_t u) {
158 u = u & 0xf;
159 return u < 10 ? '0' + u : 'a' + (u - 10);
162 public:
163 explicit EscapedString(const char* aStr)
164 : mUnownedStr(nullptr), mOwnedStr(nullptr) {
165 const char* p;
167 // First, see if we need to modify the string.
168 size_t nExtra = 0;
169 p = aStr;
170 while (true) {
171 uint8_t u = *p; // ensure it can't be interpreted as negative
172 if (u == 0) {
173 break;
175 if (detail::gTwoCharEscapes[u]) {
176 nExtra += 1;
177 } else if (u <= 0x1f) {
178 nExtra += 5;
180 p++;
183 if (nExtra == 0) {
184 // No escapes needed. Easy.
185 mIsOwned = false;
186 mUnownedStr = aStr;
187 mLen = p - aStr;
188 return;
191 // Escapes are needed. We'll create a new string.
192 mIsOwned = true;
193 mLen = (p - aStr) + nExtra;
194 mOwnedStr = MakeUnique<char[]>(mLen + 1);
196 p = aStr;
197 size_t i = 0;
199 while (true) {
200 uint8_t u = *p; // ensure it can't be interpreted as negative
201 if (u == 0) {
202 mOwnedStr[i] = 0;
203 break;
205 if (detail::gTwoCharEscapes[u]) {
206 mOwnedStr[i++] = '\\';
207 mOwnedStr[i++] = detail::gTwoCharEscapes[u];
208 } else if (u <= 0x1f) {
209 mOwnedStr[i++] = '\\';
210 mOwnedStr[i++] = 'u';
211 mOwnedStr[i++] = '0';
212 mOwnedStr[i++] = '0';
213 mOwnedStr[i++] = hexDigitToAsciiChar((u & 0x00f0) >> 4);
214 mOwnedStr[i++] = hexDigitToAsciiChar(u & 0x000f);
215 } else {
216 mOwnedStr[i++] = u;
218 p++;
222 ~EscapedString() { SanityCheck(); }
224 const char* get() const {
225 SanityCheck();
226 return mIsOwned ? mOwnedStr.get() : mUnownedStr;
229 constexpr size_t len() const { return mLen; }
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
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
249 void Indent() {
250 for (size_t i = 0; i < mDepth; i++) {
251 mWriter->Write(" ");
255 // Adds whatever is necessary (maybe a comma, and then a newline and
256 // whitespace) to separate an item (property or element) from what's come
257 // before.
258 void Separator() {
259 if (mNeedComma[mDepth]) {
260 mWriter->Write(",");
262 if (mDepth > 0 && mNeedNewlines[mDepth]) {
263 mWriter->Write("\n");
264 Indent();
265 } else if (mNeedComma[mDepth]) {
266 mWriter->Write(" ");
270 void PropertyNameAndColon(const char* aName) {
271 EscapedString escapedName(aName);
272 mWriter->WriteLiteral("\"");
273 mWriter->Write(escapedName.get(), escapedName.len());
274 mWriter->WriteLiteral("\": ");
277 void Scalar(const char* aMaybePropertyName, const char* aStringValue) {
278 Scalar(aMaybePropertyName, aStringValue, strlen(aStringValue));
281 void Scalar(const char* aMaybePropertyName, const char* aStringValue,
282 size_t aStringLen) {
283 Separator();
284 if (aMaybePropertyName) {
285 PropertyNameAndColon(aMaybePropertyName);
287 mWriter->Write(aStringValue, aStringLen);
288 mNeedComma[mDepth] = true;
291 void QuotedScalar(const char* aMaybePropertyName, const char* aStringValue,
292 size_t aStringLen) {
293 Separator();
294 if (aMaybePropertyName) {
295 PropertyNameAndColon(aMaybePropertyName);
297 mWriter->WriteLiteral("\"");
298 mWriter->Write(aStringValue, aStringLen);
299 mWriter->WriteLiteral("\"");
300 mNeedComma[mDepth] = true;
303 void NewVectorEntries() {
304 // If these tiny allocations OOM we might as well just crash because we
305 // must be in serious memory trouble.
306 MOZ_RELEASE_ASSERT(mNeedComma.resizeUninitialized(mDepth + 1));
307 MOZ_RELEASE_ASSERT(mNeedNewlines.resizeUninitialized(mDepth + 1));
308 mNeedComma[mDepth] = false;
309 mNeedNewlines[mDepth] = true;
312 void StartCollection(const char* aMaybePropertyName, const char* aStartChar,
313 CollectionStyle aStyle = MultiLineStyle) {
314 Separator();
315 if (aMaybePropertyName) {
316 PropertyNameAndColon(aMaybePropertyName);
318 mWriter->Write(aStartChar);
319 mNeedComma[mDepth] = true;
320 mDepth++;
321 NewVectorEntries();
322 mNeedNewlines[mDepth] =
323 mNeedNewlines[mDepth - 1] && aStyle == MultiLineStyle;
326 // Adds the whitespace and closing char necessary to end a collection.
327 void EndCollection(const char* aEndChar) {
328 MOZ_ASSERT(mDepth > 0);
329 if (mNeedNewlines[mDepth]) {
330 mWriter->WriteLiteral("\n");
331 mDepth--;
332 Indent();
333 } else {
334 mDepth--;
336 mWriter->Write(aEndChar);
339 public:
340 explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter)
341 : mWriter(std::move(aWriter)), mNeedComma(), mNeedNewlines(), mDepth(0) {
342 NewVectorEntries();
345 // Returns the JSONWriteFunc passed in at creation, for temporary use. The
346 // JSONWriter object still owns the JSONWriteFunc.
347 JSONWriteFunc* WriteFunc() const { return mWriter.get(); }
349 // For all the following functions, the "Prints:" comment indicates what the
350 // basic output looks like. However, it doesn't indicate the whitespace and
351 // trailing commas, which are automatically added as required.
353 // All property names and string properties are escaped as necessary.
355 // Prints: {
356 void Start(CollectionStyle aStyle = MultiLineStyle) {
357 StartCollection(nullptr, "{", aStyle);
360 // Prints: }
361 void End() { EndCollection("}\n"); }
363 // Prints: "<aName>": null
364 void NullProperty(const char* aName) { Scalar(aName, "null"); }
366 // Prints: null
367 void NullElement() { NullProperty(nullptr); }
369 // Prints: "<aName>": <aBool>
370 void BoolProperty(const char* aName, bool aBool) {
371 const char* val = aBool ? "true" : "false";
372 Scalar(aName, val);
375 // Prints: <aBool>
376 void BoolElement(bool aBool) { BoolProperty(nullptr, aBool); }
378 // Prints: "<aName>": <aInt>
379 void IntProperty(const char* aName, int64_t aInt) {
380 char buf[64];
381 int len = SprintfLiteral(buf, "%" PRId64, aInt);
382 if (len > 0) {
383 Scalar(aName, buf, static_cast<size_t>(len));
387 // Prints: <aInt>
388 void IntElement(int64_t aInt) { IntProperty(nullptr, aInt); }
390 // Prints: "<aName>": <aDouble>
391 void DoubleProperty(const char* aName, double aDouble) {
392 static const size_t buflen = 64;
393 char buf[buflen];
394 const double_conversion::DoubleToStringConverter& converter =
395 double_conversion::DoubleToStringConverter::EcmaScriptConverter();
396 double_conversion::StringBuilder builder(buf, buflen);
397 converter.ToShortest(aDouble, &builder);
398 const char* val = builder.Finalize();
399 Scalar(aName, val);
402 // Prints: <aDouble>
403 void DoubleElement(double aDouble) { DoubleProperty(nullptr, aDouble); }
405 // Prints: "<aName>": "<aStr>"
406 void StringProperty(const char* aName, const char* aStr) {
407 EscapedString escapedStr(aStr);
408 QuotedScalar(aName, escapedStr.get(), escapedStr.len());
411 // Prints: "<aStr>"
412 void StringElement(const char* aStr) { StringProperty(nullptr, aStr); }
414 // Prints: "<aName>": [
415 void StartArrayProperty(const char* aName,
416 CollectionStyle aStyle = MultiLineStyle) {
417 StartCollection(aName, "[", aStyle);
420 // Prints: [
421 void StartArrayElement(CollectionStyle aStyle = MultiLineStyle) {
422 StartArrayProperty(nullptr, aStyle);
425 // Prints: ]
426 void EndArray() { EndCollection("]"); }
428 // Prints: "<aName>": {
429 void StartObjectProperty(const char* aName,
430 CollectionStyle aStyle = MultiLineStyle) {
431 StartCollection(aName, "{", aStyle);
434 // Prints: {
435 void StartObjectElement(CollectionStyle aStyle = MultiLineStyle) {
436 StartObjectProperty(nullptr, aStyle);
439 // Prints: }
440 void EndObject() { EndCollection("}"); }
443 } // namespace mozilla
445 #endif /* mozilla_JSONWriter_h */