Coverage Report

Created: 2018-09-25 14:53

/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