Bug 1067699 (part 2) - Add mfbt/JSONWriter.h and use it for memory reporting. r=froydnj.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 14 Sep 2014 23:36:18 -0700
changeset 255156 a343e971a0f3b3f17a3f9fa0d8d97a2e723610f5
parent 255155 59244e384308fee3eff9b102a6f325fc370fab2e
child 255157 cb3f32596e705dcd0af9c7d9c781ac77b006a114
push id32593
push usernnethercote@mozilla.com
push dateWed, 17 Sep 2014 23:58:36 +0000
treeherdertry@cb3f32596e70 [default view] [failures only]
reviewersfroydnj
bugs1067699
milestone35.0a1
Bug 1067699 (part 2) - Add mfbt/JSONWriter.h and use it for memory reporting. r=froydnj.
mfbt/JSONWriter.cpp
mfbt/JSONWriter.h
mfbt/moz.build
mfbt/tests/TestJSONWriter.cpp
mfbt/tests/moz.build
xpcom/base/nsIMemoryInfoDumper.idl
xpcom/base/nsMemoryInfoDumper.cpp
new file mode 100644
--- /dev/null
+++ b/mfbt/JSONWriter.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/JSONWriter.h"
+
+namespace mozilla {
+
+// The chars with non-'___' entries in this table are those that can be
+// represented with a two-char escape sequence. The value is the second char in
+// the sequence, that which follows the initial backslash.
+#define ___ 0
+/*static*/ const char JSONWriter::EscapedString::mTwoCharEscapes[256] = {
+/*          0    1    2    3    4    5    6    7    8    9 */
+/*   0+ */ ___, ___, ___, ___, ___, ___, ___, ___, 'b', 't',
+/*  10+ */ 'n', ___, 'f', 'r', ___, ___, ___, ___, ___, ___,
+/*  20+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  30+ */ ___, ___, ___, ___, '"', ___, ___, ___, ___, ___,
+/*  40+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  50+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  60+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  70+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  80+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/*  90+ */ ___, ___,'\\', ___, ___, ___, ___, ___, ___, ___,
+/* 100+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 110+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 120+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 130+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 140+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 150+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 160+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 170+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 180+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 190+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 200+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 210+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 220+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 230+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 240+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
+/* 250+ */ ___, ___, ___, ___, ___, ___
+};
+#undef ___
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/mfbt/JSONWriter.h
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* A JSON pretty-printer class. */
+
+// A typical JSON-writing library requires you to first build up a data
+// structure that represents a JSON object and then serialize it (to file, or
+// somewhere else). This approach makes for a clean API, but building the data
+// structure takes up memory. Sometimes that isn't desirable, such as when the
+// JSON data is produced for memory reporting.
+//
+// The JSONWriter class instead allows JSON data to be written out
+// incrementally without building up large data structures.
+//
+// The API is slightly uglier than you would see in a typical JSON-writing
+// library, but still fairly easy to use. It's possible to generate invalid
+// JSON with JSONWriter, but typically the most basic testing will identify any
+// such problems.
+//
+// Similarly, there are no RAII facilities for automatically closing objects
+// and arrays. These would be nice if you are generating all your code within
+// nested functions, but in other cases you'd have to maintain an explicit
+// stack of RAII objects and manually unwind it, which is no better than just
+// calling "end" functions. Furthermore, the consequences of forgetting to
+// close an object or array are obvious and, again, will be identified via
+// basic testing, unlike other cases where RAII is typically used (e.g. smart
+// pointers) and the consequences of defects are more subtle.
+//
+// Importantly, the class does solve the two hard problems of JSON
+// pretty-printing, which are (a) correctly escaping strings, and (b) adding
+// appropriate indentation and commas between items.
+//
+// Strings used (for property names and string property values) are |const
+// char*| throughout, and can be ASCII or UTF-8.
+//
+// EXAMPLE
+// -------
+// Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The
+// following code:
+//
+//   JSONWriter w(MakeUnique<MyWriteFunc>());
+//   w.Start();
+//   {
+//     w.NullProperty("null");
+//     w.BoolProperty("bool", true);
+//     w.IntProperty("int", 1);
+//     w.StringProperty("string", "hello");
+//     w.StartArrayProperty("array");
+//     {
+//       w.DoubleElement(3.4);
+//       w.StartObjectElement();
+//       {
+//         w.PointerProperty("ptr", (void*)0x12345678);
+//       }
+//       w.EndObjectElement();
+//     }
+//     w.EndArrayProperty();
+//   }
+//   w.End();
+//
+// will produce pretty-printed output for the following JSON object:
+//
+//  {
+//   "null": null,
+//   "bool": true,
+//   "int": 1,
+//   "string": "hello",
+//   "array": [
+//    3.4,
+//    {
+//     "ptr": "0x12345678"
+//    }
+//   ]
+//  }
+//
+// The nesting in the example code is obviously optional, but can aid
+// readability.
+
+#ifndef mozilla_JSONWriter_h
+#define mozilla_JSONWriter_h
+
+#include "mozilla/double-conversion.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#include <stdio.h>
+
+namespace mozilla {
+
+// A quasi-functor for JSONWriter. We don't use a true functor because that
+// requires templatizing JSONWriter, and the templatization seeps to lots of
+// places we don't want it to.
+class JSONWriteFunc
+{
+public:
+  virtual void Write(const char* aStr) = 0;
+  virtual ~JSONWriteFunc() {}
+};
+
+class JSONWriter
+{
+  // From http://www.ietf.org/rfc/rfc4627.txt:
+  //
+  //   "All Unicode characters may be placed within the quotation marks except
+  //   for the characters that must be escaped: quotation mark, reverse
+  //   solidus, and the control characters (U+0000 through U+001F)."
+  //
+  // This implementation uses two-char escape sequences where possible, namely:
+  //
+  //   \", \\, \b, \f, \n, \r, \t
+  //
+  // All control characters not in the above list are represented with a
+  // six-char escape sequence, e.g. '\u000b' (a.k.a. '\v').
+  //
+  class EscapedString
+  {
+    // Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned|
+    // indicates which one is in use. They're not within a union because that
+    // wouldn't work with UniquePtr.
+    bool mIsOwned;
+    const char* mUnownedStr;
+    UniquePtr<char[]> mOwnedStr;
+
+    static MFBT_DATA const char mTwoCharEscapes[256];
+
+    void SanityCheck() const
+    {
+      MOZ_ASSERT_IF( mIsOwned,  mOwnedStr.get() && !mUnownedStr);
+      MOZ_ASSERT_IF(!mIsOwned, !mOwnedStr.get() &&  mUnownedStr);
+    }
+
+    static char hexDigitToAsciiChar(uint8_t u)
+    {
+      u = u & 0xf;
+      return u < 10 ? '0' + u : 'a' + (u - 10);
+    }
+
+  public:
+    EscapedString(const char* aStr)
+      : mUnownedStr(nullptr)
+      , mOwnedStr(nullptr)
+    {
+      const char* p;
+
+      // First, see if we need to modify the string.
+      size_t nExtra = 0;
+      p = aStr;
+      while (true) {
+        uint8_t u = *p;   // ensure it can't be interpreted as negative
+        if (u == 0) {
+          break;
+        }
+        if (mTwoCharEscapes[u]) {
+          nExtra += 1;
+        } else if (u <= 0x1f) {
+          nExtra += 5;
+        }
+        p++;
+      }
+
+      if (nExtra == 0) {
+        // No escapes needed. Easy.
+        mIsOwned = false;
+        mUnownedStr = aStr;
+        return;
+      }
+
+      // Escapes are needed. We'll create a new string.
+      mIsOwned = true;
+      size_t len = (p - aStr) + nExtra;
+      mOwnedStr = MakeUnique<char[]>(len + 1);
+
+      p = aStr;
+      size_t i = 0;
+
+      while (true) {
+        uint8_t u = *p;   // ensure it can't be interpreted as negative
+        if (u == 0) {
+          mOwnedStr[i] = 0;
+          break;
+        }
+        if (mTwoCharEscapes[u]) {
+          mOwnedStr[i++] = '\\';
+          mOwnedStr[i++] = mTwoCharEscapes[u];
+        } else if (u <= 0x1f) {
+          mOwnedStr[i++] = '\\';
+          mOwnedStr[i++] = 'u';
+          mOwnedStr[i++] = '0';
+          mOwnedStr[i++] = '0';
+          mOwnedStr[i++] = hexDigitToAsciiChar((u & 0x00f0) >> 4);
+          mOwnedStr[i++] = hexDigitToAsciiChar(u & 0x000f);
+        } else {
+          mOwnedStr[i++] = u;
+        }
+        p++;
+      }
+    }
+
+    ~EscapedString()
+    {
+      SanityCheck();
+    }
+
+    const char* get() const
+    {
+      SanityCheck();
+      return mIsOwned ? mOwnedStr.get() : mUnownedStr;
+    }
+  };
+
+  const UniquePtr<JSONWriteFunc> mWriter;
+  Vector<bool, 8> mNeedComma;     // do we need a comma at depth N?
+  size_t mDepth;                  // the current nesting depth
+
+  void Indent()
+  {
+    for (size_t i = 0; i < mDepth; i++) {
+      mWriter->Write(" ");
+    }
+  }
+
+  // Adds whatever is necessary (maybe a comma, and then a newline and
+  // whitespace) to separate an item (property or element) from what's come
+  // before.
+  void Separator()
+  {
+    if (mNeedComma[mDepth]) {
+      mWriter->Write(",");
+    }
+    if (mDepth > 0) {
+      mWriter->Write("\n");
+    }
+    Indent();
+  }
+
+  void PropertyNameAndColon(const char* aName)
+  {
+    EscapedString escapedName(aName);
+    mWriter->Write("\"");
+    mWriter->Write(escapedName.get());
+    mWriter->Write("\": ");
+  }
+
+  void Scalar(const char* aMaybePropertyName, const char* aStringValue)
+  {
+    Separator();
+    if (aMaybePropertyName) {
+      PropertyNameAndColon(aMaybePropertyName);
+    }
+    mWriter->Write(aStringValue);
+    mNeedComma[mDepth] = true;
+  }
+
+  void QuotedScalar(const char* aMaybePropertyName, const char* aStringValue)
+  {
+    Separator();
+    if (aMaybePropertyName) {
+      PropertyNameAndColon(aMaybePropertyName);
+    }
+    mWriter->Write("\"");
+    mWriter->Write(aStringValue);
+    mWriter->Write("\"");
+    mNeedComma[mDepth] = true;
+  }
+
+  void NewCommaEntry()
+  {
+    // If this tiny allocation OOMs we might as well just crash because we must
+    // be in serious memory trouble.
+    MOZ_RELEASE_ASSERT(mNeedComma.growByUninitialized(1));
+    mNeedComma[mDepth] = false;
+  }
+
+  void StartCollection(const char* aMaybePropertyName, const char* aStartChar)
+  {
+    Separator();
+    if (aMaybePropertyName) {
+      mWriter->Write("\"");
+      mWriter->Write(aMaybePropertyName);
+      mWriter->Write("\": ");
+    }
+    mWriter->Write(aStartChar);
+    mNeedComma[mDepth] = true;
+    mDepth++;
+    NewCommaEntry();
+  }
+
+  // Adds the whitespace and closing char necessary to end a collection.
+  void EndCollection(const char* aEndChar)
+  {
+    mDepth--;
+    mWriter->Write("\n");
+    Indent();
+    mWriter->Write(aEndChar);
+  }
+
+public:
+  explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter)
+    : mWriter(Move(aWriter))
+    , mNeedComma()
+    , mDepth(0)
+  {
+    NewCommaEntry();
+  }
+
+  // Returns the JSONWriteFunc passed in at creation, for temporary use. The
+  // JSONWriter object still owns the JSONWriteFunc.
+  JSONWriteFunc* WriteFunc() const { return mWriter.get(); }
+
+  // For all the following functions, the "Prints:" comment indicates what the
+  // basic output looks like. However, it doesn't indicate the indentation and
+  // trailing commas, which are automatically added as required.
+  //
+  // All property names and string properties are escaped as necessary.
+
+  // Prints: {
+  void Start() { StartCollection(nullptr, "{"); }
+
+  // Prints: }\n
+  void End() { EndCollection("}\n"); }
+
+  // Prints: "<aName>": null
+  void NullProperty(const char* aName)
+  {
+    Scalar(aName, "null");
+  }
+
+  // Prints: null
+  void NullElement() { NullProperty(nullptr); }
+
+  // Prints: "<aName>": <aBool>
+  void BoolProperty(const char* aName, bool aBool)
+  {
+    Scalar(aName, aBool ? "true" : "false");
+  }
+
+  // Prints: <aBool>
+  void BoolElement(bool aBool) { BoolProperty(nullptr, aBool); }
+
+  // Prints: "<aName>": <aInt>
+  void IntProperty(const char* aName, int64_t aInt)
+  {
+    char buf[64];
+    sprintf(buf, "%" PRId64, aInt);
+    Scalar(aName, buf);
+  }
+
+  // Prints: <aInt>
+  void IntElement(int64_t aInt) { IntProperty(nullptr, aInt); }
+
+  // Prints: "<aName>": <aDouble>
+  void DoubleProperty(const char* aName, double aDouble)
+  {
+    static const size_t buflen = 64;
+    char buf[buflen];
+    const double_conversion::DoubleToStringConverter &converter =
+      double_conversion::DoubleToStringConverter::EcmaScriptConverter();
+    double_conversion::StringBuilder builder(buf, buflen);
+    converter.ToShortest(aDouble, &builder);
+    Scalar(aName, builder.Finalize());
+  }
+
+  // Prints: <aDouble>
+  void DoubleElement(double aDouble) { DoubleProperty(nullptr, aDouble); }
+
+  // Prints: "<aName>": "<aStr>"
+  void StringProperty(const char* aName, const char* aStr)
+  {
+    EscapedString escapedStr(aStr);
+    QuotedScalar(aName, escapedStr.get());
+  }
+
+  // Prints: "<aStr>"
+  void StringElement(const char* aStr) { StringProperty(nullptr, aStr); }
+
+  // Prints: "<aName>": "<aPtr>"
+  // The pointer is printed as a hexadecimal integer with a leading '0x'.
+  void PointerProperty(const char* aName, const void* aPtr)
+  {
+    char buf[32];
+    sprintf(buf, "0x%" PRIxPTR, uintptr_t(aPtr));
+    QuotedScalar(aName, buf);
+  }
+
+  // Prints: "<aPtr>"
+  // The pointer is printed as a hexadecimal integer with a leading '0x'.
+  void PointerElement(const void* aPtr) { PointerProperty(nullptr, aPtr); }
+
+  // Prints: "<aName>": [
+  void StartArrayProperty(const char* aName) { StartCollection(aName, "["); }
+
+  // Prints: [
+  void StartArrayElement() { StartArrayProperty(nullptr); }
+
+  // Prints: ]
+  void EndArray() { EndCollection("]"); }
+
+  // Prints: "<aName>": {
+  void StartObjectProperty(const char* aName) { StartCollection(aName, "{"); }
+
+  // Prints: {
+  void StartObjectElement() { StartObjectProperty(nullptr); }
+
+  // Prints: }
+  void EndObject() { EndCollection("}"); }
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_JSONWriter_h */
+
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -32,16 +32,17 @@ EXPORTS.mozilla = [
     'Endian.h',
     'EnumeratedArray.h',
     'EnumSet.h',
     'FloatingPoint.h',
     'GuardObjects.h',
     'HashFunctions.h',
     'IntegerPrintfMacros.h',
     'IntegerTypeTraits.h',
+    'JSONWriter.h',
     'Likely.h',
     'LinkedList.h',
     'MacroArgs.h',
     'MacroForEach.h',
     'MathAlgorithms.h',
     'Maybe.h',
     'MaybeOneOf.h',
     'MemoryChecking.h',
@@ -92,16 +93,17 @@ UNIFIED_SOURCES = [
     'double-conversion/cached-powers.cc',
     'double-conversion/diy-fp.cc',
     'double-conversion/double-conversion.cc',
     'double-conversion/fast-dtoa.cc',
     'double-conversion/fixed-dtoa.cc',
     'double-conversion/strtod.cc',
     'FloatingPoint.cpp',
     'HashFunctions.cpp',
+    'JSONWriter.cpp',
     'Poison.cpp',
     'SHA1.cpp',
     'TaggedAnonymousMemory.cpp',
     'unused.cpp',
 ]
 
 DEFINES['IMPL_MFBT'] = True
 
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestJSONWriter.cpp
@@ -0,0 +1,417 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/Assertions.h"
+#include "mozilla/JSONWriter.h"
+#include "mozilla/UniquePtr.h"
+#include <stdio.h>
+#include <string.h>
+
+using mozilla::JSONWriteFunc;
+using mozilla::JSONWriter;
+using mozilla::MakeUnique;
+
+// This writes all the output into a big buffer.
+struct StringWriteFunc : public JSONWriteFunc
+{
+  const static size_t kLen = 100000;
+  char mBuf[kLen];
+  char* mPtr;
+
+  StringWriteFunc() : mPtr(mBuf) {}
+
+  void Write(const char* aStr)
+  {
+    char* last = mPtr + strlen(aStr);    // where the nul will be added
+
+    // If you change this test and this assertion fails, just make kLen bigger.
+    MOZ_RELEASE_ASSERT(last < mBuf + kLen);
+    sprintf(mPtr, "%s", aStr);
+    mPtr = last;
+  }
+};
+
+void Check(JSONWriteFunc* aFunc, const char* aExpected)
+{
+  const char* actual = static_cast<StringWriteFunc*>(aFunc)->mBuf;
+  if (strcmp(aExpected, actual) != 0) {
+    fprintf(stderr,
+            "---- EXPECTED ----\n<<<%s>>>\n"
+            "---- ACTUAL ----\n<<<%s>>>\n",
+            aExpected, actual);
+    MOZ_RELEASE_ASSERT(false, "expected and actual output don't match");
+  }
+}
+
+// Note: to convert actual output into |expected| strings that C++ can handle,
+// apply the following substitutions, in order, to each line.
+// - s/\\/\\\\/g    # escapes backslashes
+// - s/"/\\"/g      # escapes quotes
+// - s/$/\\n\\/     # adds a newline and string continuation char to each line
+
+void TestBasicProperties()
+{
+  const char* expected = "\
+{\n\
+ \"null\": null,\n\
+ \"bool1\": true,\n\
+ \"bool2\": false,\n\
+ \"int1\": 123,\n\
+ \"int2\": -123,\n\
+ \"int3\": -123456789000,\n\
+ \"double1\": 1.2345,\n\
+ \"double2\": -3,\n\
+ \"double3\": 1e-7,\n\
+ \"double4\": 1.1111111111111111e+21,\n\
+ \"string1\": \"\",\n\
+ \"string2\": \"1234\",\n\
+ \"string3\": \"hello\",\n\
+ \"string4\": \"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
+ \"ptr1\": \"0x0\",\n\
+ \"ptr2\": \"0xdeadbeef\",\n\
+ \"ptr3\": \"0xfacade\",\n\
+ \"len 0 array\": [\n\
+ ],\n\
+ \"len 1 array\": [\n\
+  1\n\
+ ],\n\
+ \"len 5 array\": [\n\
+  1,\n\
+  2,\n\
+  3,\n\
+  4,\n\
+  5\n\
+ ],\n\
+ \"len 0 object\": {\n\
+ },\n\
+ \"len 1 object\": {\n\
+  \"one\": 1\n\
+ },\n\
+ \"len 5 object\": {\n\
+  \"one\": 1,\n\
+  \"two\": 2,\n\
+  \"three\": 3,\n\
+  \"four\": 4,\n\
+  \"five\": 5\n\
+ }\n\
+}\n\
+";
+
+  JSONWriter w(MakeUnique<StringWriteFunc>());
+
+  w.Start();
+  {
+    w.NullProperty("null");
+
+    w.BoolProperty("bool1", true);
+    w.BoolProperty("bool2", false);
+
+    w.IntProperty("int1", 123);
+    w.IntProperty("int2", -0x7b);
+    w.IntProperty("int3", -123456789000ll);
+
+    w.DoubleProperty("double1", 1.2345);
+    w.DoubleProperty("double2", -3);
+    w.DoubleProperty("double3", 1e-7);
+    w.DoubleProperty("double4", 1.1111111111111111e+21);
+
+    w.StringProperty("string1", "");
+    w.StringProperty("string2", "1234");
+    w.StringProperty("string3", "hello");
+    w.StringProperty("string4", "\" \\ \a \b \t \n \v \f \r");
+
+    w.PointerProperty("ptr1", (void*)0x0);
+    w.PointerProperty("ptr2", (void*)0xdeadbeef);
+    w.PointerProperty("ptr3", (void*)0xFaCaDe);
+
+    w.StartArrayProperty("len 0 array");
+    w.EndArray();
+
+    w.StartArrayProperty("len 1 array");
+    {
+      w.IntElement(1);
+    }
+    w.EndArray();
+
+    w.StartArrayProperty("len 5 array");
+    {
+      w.IntElement(1);
+      w.IntElement(2);
+      w.IntElement(3);
+      w.IntElement(4);
+      w.IntElement(5);
+    }
+    w.EndArray();
+
+    w.StartObjectProperty("len 0 object");
+    w.EndObject();
+
+    w.StartObjectProperty("len 1 object");
+    {
+      w.IntProperty("one", 1);
+    }
+    w.EndObject();
+
+    w.StartObjectProperty("len 5 object");
+    {
+      w.IntProperty("one", 1);
+      w.IntProperty("two", 2);
+      w.IntProperty("three", 3);
+      w.IntProperty("four", 4);
+      w.IntProperty("five", 5);
+    }
+    w.EndObject();
+  }
+  w.End();
+
+  Check(w.WriteFunc(), expected);
+}
+
+void TestBasicElements()
+{
+  const char* expected = "\
+{\n\
+ \"array\": [\n\
+  null,\n\
+  true,\n\
+  false,\n\
+  123,\n\
+  -123,\n\
+  -123456789000,\n\
+  1.2345,\n\
+  -3,\n\
+  1e-7,\n\
+  1.1111111111111111e+21,\n\
+  \"\",\n\
+  \"1234\",\n\
+  \"hello\",\n\
+  \"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
+  \"0x0\",\n\
+  \"0xdeadbeef\",\n\
+  \"0xfacade\",\n\
+  [\n\
+  ],\n\
+  [\n\
+   1\n\
+  ],\n\
+  [\n\
+   1,\n\
+   2,\n\
+   3,\n\
+   4,\n\
+   5\n\
+  ],\n\
+  {\n\
+  },\n\
+  {\n\
+   \"one\": 1\n\
+  },\n\
+  {\n\
+   \"one\": 1,\n\
+   \"two\": 2,\n\
+   \"three\": 3,\n\
+   \"four\": 4,\n\
+   \"five\": 5\n\
+  }\n\
+ ]\n\
+}\n\
+";
+
+  JSONWriter w(MakeUnique<StringWriteFunc>());
+
+  w.Start();
+  w.StartArrayProperty("array");
+  {
+    w.NullElement();
+
+    w.BoolElement(true);
+    w.BoolElement(false);
+
+    w.IntElement(123);
+    w.IntElement(-0x7b);
+    w.IntElement(-123456789000ll);
+
+    w.DoubleElement(1.2345);
+    w.DoubleElement(-3);
+    w.DoubleElement(1e-7);
+    w.DoubleElement(1.1111111111111111e+21);
+
+    w.StringElement("");
+    w.StringElement("1234");
+    w.StringElement("hello");
+    w.StringElement("\" \\ \a \b \t \n \v \f \r");
+
+    w.PointerElement((void*)0x0);
+    w.PointerElement((void*)0xdeadbeef);
+    w.PointerElement((void*)0xFaCaDe);
+
+    w.StartArrayElement();
+    w.EndArray();
+
+    w.StartArrayElement();
+    {
+      w.IntElement(1);
+    }
+    w.EndArray();
+
+    w.StartArrayElement();
+    {
+      w.IntElement(1);
+      w.IntElement(2);
+      w.IntElement(3);
+      w.IntElement(4);
+      w.IntElement(5);
+    }
+    w.EndArray();
+
+    w.StartObjectElement();
+    w.EndObject();
+
+    w.StartObjectElement();
+    {
+      w.IntProperty("one", 1);
+    }
+    w.EndObject();
+
+    w.StartObjectElement();
+    {
+      w.IntProperty("one", 1);
+      w.IntProperty("two", 2);
+      w.IntProperty("three", 3);
+      w.IntProperty("four", 4);
+      w.IntProperty("five", 5);
+    }
+    w.EndObject();
+  }
+  w.EndArray();
+  w.End();
+
+  Check(w.WriteFunc(), expected);
+}
+
+void TestStringEscaping()
+{
+  const char* expected = "\
+{\n\
+ \"ascii\": \"~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\\\"! \\u001f\\u001e\\u001d\\u001c\\u001b\\u001a\\u0019\\u0018\\u0017\\u0016\\u0015\\u0014\\u0013\\u0012\\u0011\\u0010\\u000f\\u000e\\r\\f\\u000b\\n\\t\\b\\u0007\\u0006\\u0005\\u0004\\u0003\\u0002\\u0001\",\n\
+ \"مرحبا هناك\": true,\n\
+ \"բարեւ չկա\": -123,\n\
+ \"你好\": 1.234,\n\
+ \"γεια εκεί\": \"سلام\",\n\
+ \"halló þarna\": \"0x1234\",\n\
+ \"こんにちは\": {\n\
+  \"привет\": [\n\
+  ]\n\
+ }\n\
+}\n\
+";
+
+  JSONWriter w(MakeUnique<StringWriteFunc>());
+
+  // Test the string escaping behaviour.
+  w.Start();
+  {
+    // Test all 127 ascii values. Do it in reverse order so that the 0
+    // at the end serves as the null char.
+    char buf[128];
+    for (int i = 0; i < 128; i++) {
+      buf[i] = 127 - i;
+    }
+    w.StringProperty("ascii", buf);
+
+    // Test lots of unicode stuff. Note that this file is encoded as UTF-8.
+    w.BoolProperty("مرحبا هناك", true);
+    w.IntProperty("բարեւ չկա", -123);
+    w.DoubleProperty("你好", 1.234);
+    w.StringProperty("γεια εκεί", "سلام");
+    w.PointerProperty("halló þarna", (void*)0x1234);
+    w.StartObjectProperty("こんにちは");
+    {
+      w.StartArrayProperty("привет");
+      w.EndArray();
+    }
+    w.EndObject();
+  }
+  w.End();
+
+  Check(w.WriteFunc(), expected);
+}
+
+void TestDeepNesting()
+{
+  const char* expected = "\
+{\n\
+ \"a\": [\n\
+  {\n\
+   \"a\": [\n\
+    {\n\
+     \"a\": [\n\
+      {\n\
+       \"a\": [\n\
+        {\n\
+         \"a\": [\n\
+          {\n\
+           \"a\": [\n\
+            {\n\
+             \"a\": [\n\
+              {\n\
+               \"a\": [\n\
+                {\n\
+                 \"a\": [\n\
+                  {\n\
+                   \"a\": [\n\
+                    {\n\
+                    }\n\
+                   ]\n\
+                  }\n\
+                 ]\n\
+                }\n\
+               ]\n\
+              }\n\
+             ]\n\
+            }\n\
+           ]\n\
+          }\n\
+         ]\n\
+        }\n\
+       ]\n\
+      }\n\
+     ]\n\
+    }\n\
+   ]\n\
+  }\n\
+ ]\n\
+}\n\
+";
+
+  JSONWriter w(MakeUnique<StringWriteFunc>());
+
+  w.Start();
+  {
+    static const int n = 10;
+    for (int i = 0; i < n; i++) {
+      w.StartArrayProperty("a");
+      w.StartObjectElement();
+    }
+    for (int i = 0; i < n; i++) {
+      w.EndObject();
+      w.EndArray();
+    }
+  }
+  w.End();
+
+  Check(w.WriteFunc(), expected);
+}
+
+int main(void)
+{
+  TestBasicProperties();
+  TestBasicElements();
+  TestStringEscaping();
+  TestDeepNesting();
+
+  return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -13,16 +13,17 @@ CppUnitTests([
     'TestCeilingFloor',
     'TestCheckedInt',
     'TestCountPopulation',
     'TestCountZeroes',
     'TestEndian',
     'TestEnumSet',
     'TestFloatingPoint',
     'TestIntegerPrintfMacros',
+    'TestJSONWriter',
     'TestMacroArgs',
     'TestMacroForEach',
     'TestMaybe',
     'TestPair',
     'TestRefPtr',
     'TestRollingMean',
     'TestSHA1',
     'TestSplayTree',
--- a/xpcom/base/nsIMemoryInfoDumper.idl
+++ b/xpcom/base/nsIMemoryInfoDumper.idl
@@ -57,31 +57,37 @@ interface nsIMemoryInfoDumper : nsISuppo
    *
    * @param aFinishDumpingData The environment for the callback.
    *
    * @param aAnonymize Should the reports be anonymized?
    *
    * Sample output:
    *
    * {
-   *   "hasMozMallocUsableSize":true,
+   *   "version": 1
+   *   "hasMozMallocUsableSize": true,
    *   "reports": [
    *     {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
    *      "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
    *     {"process":"Main Process (pid 12345)", "path":"heap-allocated",
    *      "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."},
    *     {"process":"Main Process (pid 12345)", "path":"vsize",
    *      "kind":1, "units":0, "amount":10000000, "description":"Vsize."}
    *   ]
    * }
    *
    * JSON schema for the output.
    *
    * {
    *   "properties": {
+   *     "version": {
+   *       "type": "integer",
+   *       "description": "Version number of this schema.",
+   *       "required": true
+   *     },
    *     "hasMozMallocUsableSize": {
    *       "type": "boolean",
    *       "description": "nsIMemoryReporterManager::hasMozMallocUsableSize",
    *       "required": true
    *     },
    *     "reports": {
    *       "type": "array",
    *       "description": "The memory reports.",
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/JSONWriter.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/nsMemoryInfoDumper.h"
 #include "nsDumpUtils.h"
 
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "nsIConsoleService.h"
 #include "nsCycleCollector.h"
@@ -413,110 +415,16 @@ nsMemoryInfoDumper::DumpGCAndCCLogsToSin
 
   logger->SetLogSink(aSink);
 
   nsJSContext::CycleCollectNow(logger);
 
   return NS_OK;
 }
 
-namespace mozilla {
-
-#define DUMP(o, s) \
-  do { \
-    nsresult rv = (o)->Write(s); \
-    if (NS_WARN_IF(NS_FAILED(rv))) \
-      return rv; \
-  } while (0)
-
-class DumpReportCallback MOZ_FINAL : public nsIHandleReportCallback
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit DumpReportCallback(nsGZFileWriter* aWriter)
-    : mIsFirst(true)
-    , mWriter(aWriter)
-  {
-  }
-
-  NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
-                      int32_t aKind, int32_t aUnits, int64_t aAmount,
-                      const nsACString& aDescription,
-                      nsISupports* aData)
-  {
-    if (mIsFirst) {
-      DUMP(mWriter, "[");
-      mIsFirst = false;
-    } else {
-      DUMP(mWriter, ",");
-    }
-
-    nsAutoCString process;
-    if (aProcess.IsEmpty()) {
-      // If the process is empty, the report originated with the process doing
-      // the dumping.  In that case, generate the process identifier, which is of
-      // the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we don't
-      // have a process name.  If we're the main process, we let $PROCESS_NAME be
-      // "Main Process".
-      if (XRE_GetProcessType() == GeckoProcessType_Default) {
-        // We're the main process.
-        process.AssignLiteral("Main Process");
-      } else if (ContentChild* cc = ContentChild::GetSingleton()) {
-        // Try to get the process name from ContentChild.
-        cc->GetProcessName(process);
-      }
-      ContentChild::AppendProcessId(process);
-
-    } else {
-      // Otherwise, the report originated with another process and already has a
-      // process name.  Just use that.
-      process = aProcess;
-    }
-
-    DUMP(mWriter, "\n    {\"process\": \"");
-    DUMP(mWriter, process);
-
-    DUMP(mWriter, "\", \"path\": \"");
-    nsCString path(aPath);
-    path.ReplaceSubstring("\\", "\\\\");    /* <backslash> --> \\ */
-    path.ReplaceSubstring("\"", "\\\"");    // " --> \"
-    DUMP(mWriter, path);
-
-    DUMP(mWriter, "\", \"kind\": ");
-    DUMP(mWriter, nsPrintfCString("%d", aKind));
-
-    DUMP(mWriter, ", \"units\": ");
-    DUMP(mWriter, nsPrintfCString("%d", aUnits));
-
-    DUMP(mWriter, ", \"amount\": ");
-    DUMP(mWriter, nsPrintfCString("%lld", aAmount));
-
-    nsCString description(aDescription);
-    description.ReplaceSubstring("\\", "\\\\");    /* <backslash> --> \\ */
-    description.ReplaceSubstring("\"", "\\\"");    // " --> \"
-    description.ReplaceSubstring("\n", "\\n");     // <newline> --> \n
-    DUMP(mWriter, ", \"description\": \"");
-    DUMP(mWriter, description);
-    DUMP(mWriter, "\"}");
-
-    return NS_OK;
-  }
-
-private:
-  ~DumpReportCallback() {}
-
-  bool mIsFirst;
-  nsRefPtr<nsGZFileWriter> mWriter;
-};
-
-NS_IMPL_ISUPPORTS(DumpReportCallback, nsIHandleReportCallback)
-
-} // namespace mozilla
-
 static void
 MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
              int aPid, const char* aSuffix, nsACString& aResult)
 {
   aResult = nsPrintfCString("%s-%s-%d.%s",
                             aPrefix,
                             NS_ConvertUTF16toUTF8(aIdentifier).get(),
                             aPid, aSuffix);
@@ -539,94 +447,130 @@ static void
 DMDWrite(void* aState, const char* aFmt, va_list ap)
 {
   DMDWriteState* state = (DMDWriteState*)aState;
   vsnprintf(state->mBuf, state->kBufSize, aFmt, ap);
   unused << state->mGZWriter->Write(state->mBuf);
 }
 #endif
 
-static nsresult
-DumpHeader(nsIGZFileWriter* aWriter)
+// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
+// the following two problems:
+// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
+// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
+class GZWriterWrapper : public JSONWriteFunc
 {
-  // Increment this number if the format changes.
-  //
-  // This is the first write to the file, and it causes |aWriter| to allocate
-  // over 200 KiB of memory.
-  //
-  DUMP(aWriter, "{\n  \"version\": 1,\n");
+public:
+  explicit GZWriterWrapper(nsGZFileWriter* aGZWriter)
+    : mGZWriter(aGZWriter)
+  {}
 
-  DUMP(aWriter, "  \"hasMozMallocUsableSize\": ");
-
-  nsCOMPtr<nsIMemoryReporterManager> mgr =
-    do_GetService("@mozilla.org/memory-reporter-manager;1");
-  if (NS_WARN_IF(!mgr)) {
-    return NS_ERROR_UNEXPECTED;
+  void Write(const char* aStr)
+  {
+    (void)mGZWriter->Write(aStr);
   }
 
-  DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false");
-  DUMP(aWriter, ",\n");
-  DUMP(aWriter, "  \"reports\": ");
+  nsresult Finish() { return mGZWriter->Finish(); }
 
-  return NS_OK;
-}
+private:
+  nsRefPtr<nsGZFileWriter> mGZWriter;
+};
 
-static nsresult
-DumpFooter(nsIGZFileWriter* aWriter)
-{
-  DUMP(aWriter, "\n  ]\n}\n");
-
-  return NS_OK;
-}
-
-// This dumps the JSON footer and closes the file, and then calls the given
-// nsIFinishDumpingCallback.
-class FinishReportingCallback MOZ_FINAL : public nsIFinishReportingCallback
+// We need two callbacks: one that handles reports, and one that is called at
+// the end of reporting. Both the callbacks need access to the same JSONWriter,
+// so we implement both of them in this one class.
+class HandleReportAndFinishReportingCallbacks MOZ_FINAL
+  : public nsIHandleReportCallback, public nsIFinishReportingCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
-  FinishReportingCallback(nsGZFileWriter* aReportsWriter,
-                          nsIFinishDumpingCallback* aFinishDumping,
-                          nsISupports* aFinishDumpingData)
-    : mReportsWriter(aReportsWriter)
+  HandleReportAndFinishReportingCallbacks(UniquePtr<JSONWriter> aWriter,
+                                          nsIFinishDumpingCallback* aFinishDumping,
+                                          nsISupports* aFinishDumpingData)
+    : mWriter(Move(aWriter))
     , mFinishDumping(aFinishDumping)
     , mFinishDumpingData(aFinishDumpingData)
   {
   }
 
+  // This is the callback for nsIHandleReportCallback.
+  NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
+                      int32_t aKind, int32_t aUnits, int64_t aAmount,
+                      const nsACString& aDescription,
+                      nsISupports* aData)
+  {
+    nsAutoCString process;
+    if (aProcess.IsEmpty()) {
+      // If the process is empty, the report originated with the process doing
+      // the dumping.  In that case, generate the process identifier, which is
+      // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
+      // don't have a process name.  If we're the main process, we let
+      // $PROCESS_NAME be "Main Process".
+      if (XRE_GetProcessType() == GeckoProcessType_Default) {
+        // We're the main process.
+        process.AssignLiteral("Main Process");
+      } else if (ContentChild* cc = ContentChild::GetSingleton()) {
+        // Try to get the process name from ContentChild.
+        cc->GetProcessName(process);
+      }
+      ContentChild::AppendProcessId(process);
+
+    } else {
+      // Otherwise, the report originated with another process and already has a
+      // process name.  Just use that.
+      process = aProcess;
+    }
+
+    mWriter->StartObjectElement();
+    {
+      mWriter->StringProperty("process", process.get());
+      mWriter->StringProperty("path", PromiseFlatCString(aPath).get());
+      mWriter->IntProperty("kind", aKind);
+      mWriter->IntProperty("units", aUnits);
+      mWriter->IntProperty("amount", aAmount);
+      mWriter->StringProperty("description",
+                              PromiseFlatCString(aDescription).get());
+    }
+    mWriter->EndObject();
+
+    return NS_OK;
+  }
+
+  // This is the callback for nsIFinishReportingCallback.
   NS_IMETHOD Callback(nsISupports* aData)
   {
-    nsresult rv = DumpFooter(mReportsWriter);
-    NS_ENSURE_SUCCESS(rv, rv);
+    mWriter->EndArray();  // end of "reports" array
+    mWriter->End();
 
-    // The call to Finish() deallocates the memory allocated by the first DUMP()
+    // The call to Finish() deallocates the memory allocated by the first Write
     // call. Because that memory was live while the memory reporters ran and
-    // thus measured by them -- by "heap-allocated" if nothing else -- we want
-    // DMD to see it as well.  So we deliberately don't call Finish() until
+    // was measured by them -- by "heap-allocated" if nothing else -- we want
+    // DMD to see it as well. So we deliberately don't call Finish() until
     // after DMD finishes.
-    rv = mReportsWriter->Finish();
+    nsresult rv = static_cast<GZWriterWrapper*>(mWriter->WriteFunc())->Finish();
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!mFinishDumping) {
       return NS_OK;
     }
 
     return mFinishDumping->Callback(mFinishDumpingData);
   }
 
 private:
-  ~FinishReportingCallback() {}
+  ~HandleReportAndFinishReportingCallbacks() {}
 
-  nsRefPtr<nsGZFileWriter> mReportsWriter;
+  UniquePtr<JSONWriter> mWriter;
   nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
   nsCOMPtr<nsISupports> mFinishDumpingData;
 };
 
-NS_IMPL_ISUPPORTS(FinishReportingCallback, nsIFinishReportingCallback)
+NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
+                  nsIHandleReportCallback, nsIFinishReportingCallback)
 
 class TempDirFinishCallback MOZ_FINAL : public nsIFinishDumpingCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   TempDirFinishCallback(nsIFile* aReportsTmpFile,
                         const nsCString& aReportsFinalFilename)
@@ -708,38 +652,45 @@ static nsresult
 DumpMemoryInfoToFile(
   nsIFile* aReportsFile,
   nsIFinishDumpingCallback* aFinishDumping,
   nsISupports* aFinishDumpingData,
   bool aAnonymize,
   bool aMinimizeMemoryUsage,
   nsAString& aDMDIdentifier)
 {
-  nsRefPtr<nsGZFileWriter> reportsWriter = new nsGZFileWriter();
-  nsresult rv = reportsWriter->Init(aReportsFile);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Dump the memory reports to the file.
-  rv = DumpHeader(reportsWriter);
+  nsRefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
+  nsresult rv = gzWriter->Init(aReportsFile);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
+  auto jsonWriter =
+    MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
 
-  // Process reporters.
   nsCOMPtr<nsIMemoryReporterManager> mgr =
     do_GetService("@mozilla.org/memory-reporter-manager;1");
-  nsRefPtr<DumpReportCallback> dumpReport =
-    new DumpReportCallback(reportsWriter);
-  nsRefPtr<FinishReportingCallback> finishReporting =
-    new FinishReportingCallback(reportsWriter, aFinishDumping,
-                                aFinishDumpingData);
-  rv = mgr->GetReportsExtended(dumpReport, nullptr,
-                               finishReporting, nullptr,
+
+  // This is the first write to the file, and it causes |aWriter| to allocate
+  // over 200 KiB of memory.
+  jsonWriter->Start();
+  {
+    // Increment this number if the format changes.
+    jsonWriter->IntProperty("version", 1);
+    jsonWriter->BoolProperty("hasMozMallocUsableSize",
+                             mgr->GetHasMozMallocUsableSize());
+    jsonWriter->StartArrayProperty("reports");
+  }
+
+  nsRefPtr<HandleReportAndFinishReportingCallbacks>
+    handleReportAndFinishReporting =
+      new HandleReportAndFinishReportingCallbacks(Move(jsonWriter),
+                                                  aFinishDumping,
+                                                  aFinishDumpingData);
+  rv = mgr->GetReportsExtended(handleReportAndFinishReporting, nullptr,
+                               handleReportAndFinishReporting, nullptr,
                                aAnonymize,
                                aMinimizeMemoryUsage,
                                aDMDIdentifier);
   return rv;
 }
 
 NS_IMETHODIMP
 nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
@@ -796,22 +747,22 @@ nsMemoryInfoDumper::DumpMemoryInfoToTemp
   //
   // in NS_OS_TEMP_DIR for writing.  When we're finished writing the report,
   // we'll rename this file and get rid of the "incomplete-" prefix.
   //
   // We do this because we don't want scripts which poll the filesystem
   // looking for memory report dumps to grab a file before we're finished
   // writing to it.
 
-  nsCString reportsFinalFilename;
   // The "unified" indicates that we merge the memory reports from all
   // processes and write out one file, rather than a separate file for
   // each process as was the case before bug 946407.  This is so that
   // the get_about_memory.py script in the B2G repository can
   // determine when it's done waiting for files to appear.
+  nsCString reportsFinalFilename;
   MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
                reportsFinalFilename);
 
   nsCOMPtr<nsIFile> reportsTmpFile;
   nsresult rv;
   // In Android case, this function will open a file named aFilename under
   // specific folder (/data/local/tmp/memory-reports). Otherwise, it will
   // open a file named aFilename under "NS_OS_TEMP_DIR".
@@ -887,9 +838,8 @@ nsMemoryInfoDumper::DumpDMDToFile(FILE* 
   dmd::AnalyzeReports(w);
 
   rv = dmdWriter->Finish();
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }
 #endif  // MOZ_DMD
 
-#undef DUMP