Bug 1397717 - Using GenericPrinter for DEBUG-only C++ dump() APIs;r?nbp draft
authorDavid Teller <dteller@mozilla.com>
Thu, 07 Sep 2017 15:22:38 +0200
changeset 662397 9ae991c7f50c36ea503a4ff983a8d2db9f0b4aeb
parent 655774 ab2d700fda2b4934d24227216972dce9fac19b74
child 730856 de128f6382d3248b35b10d93d13b4bfa294072b4
push id79071
push userdteller@mozilla.com
push dateMon, 11 Sep 2017 17:07:03 +0000
reviewersnbp
bugs1397717
milestone57.0a1
Bug 1397717 - Using GenericPrinter for DEBUG-only C++ dump() APIs;r?nbp We have a host of DEBUG-only dump()-style APIs that output either to stderr or to a FILE*. Unfortunately, this means that we cannot use these dumps e.g. for unit tests. This patch ports most of the dump() APIs to use GenericPrinter, which is more flexible. Some parts of the code have not been ported, in particular TypeInference, which still uses stderr. MozReview-Commit-ID: A5WGOPyIPTa
js/src/builtin/TestingFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/gc/Heap.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/shell/js.cpp
js/src/vm/Printer.h
js/src/vm/SelfHosting.cpp
js/src/vm/Shape.cpp
js/src/vm/Shape.h
js/src/vm/String.cpp
js/src/vm/String.h
js/src/vm/Symbol.cpp
js/src/vm/Symbol.h
js/src/vm/TypeInference.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -3580,17 +3580,18 @@ static bool
 DumpStringRepresentation(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedString str(cx, ToString(cx, args.get(0)));
     if (!str)
         return false;
 
-    str->dumpRepresentation(stderr, 0);
+    Fprinter out(stderr);
+    str->dumpRepresentation(out, 0);
 
     args.rval().setUndefined();
     return true;
 }
 #endif
 
 static bool
 SetLazyParsingDisabled(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -618,250 +618,257 @@ ParseNode::appendOrCreateList(ParseNodeK
 
 static const char * const parseNodeNames[] = {
 #define STRINGIFY(name) #name,
     FOR_EACH_PARSE_NODE_KIND(STRINGIFY)
 #undef STRINGIFY
 };
 
 void
-frontend::DumpParseTree(ParseNode* pn, int indent)
+frontend::DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent)
 {
     if (pn == nullptr)
-        fprintf(stderr, "#NULL");
+        out.put("#NULL");
     else
-        pn->dump(indent);
+        pn->dump(out, indent);
 }
 
 static void
-IndentNewLine(int indent)
+IndentNewLine(GenericPrinter& out, int indent)
 {
-    fputc('\n', stderr);
+    out.putChar('\n');
     for (int i = 0; i < indent; ++i)
-        fputc(' ', stderr);
+        out.putChar(' ');
+}
+
+void
+ParseNode::dump(GenericPrinter& out)
+{
+    dump(out, 0);
+    out.putChar('\n');
 }
 
 void
 ParseNode::dump()
 {
-    dump(0);
-    fputc('\n', stderr);
+    js::Fprinter out(stderr);
+    dump(out);
 }
 
 void
-ParseNode::dump(int indent)
+ParseNode::dump(GenericPrinter& out, int indent)
 {
     switch (pn_arity) {
       case PN_NULLARY:
-        ((NullaryNode*) this)->dump();
+        ((NullaryNode*) this)->dump(out);
         break;
       case PN_UNARY:
-        ((UnaryNode*) this)->dump(indent);
+        ((UnaryNode*) this)->dump(out, indent);
         break;
       case PN_BINARY:
-        ((BinaryNode*) this)->dump(indent);
+        ((BinaryNode*) this)->dump(out, indent);
         break;
       case PN_TERNARY:
-        ((TernaryNode*) this)->dump(indent);
+        ((TernaryNode*) this)->dump(out, indent);
         break;
       case PN_CODE:
-        ((CodeNode*) this)->dump(indent);
+        ((CodeNode*) this)->dump(out, indent);
         break;
       case PN_LIST:
-        ((ListNode*) this)->dump(indent);
+        ((ListNode*) this)->dump(out, indent);
         break;
       case PN_NAME:
-        ((NameNode*) this)->dump(indent);
+        ((NameNode*) this)->dump(out, indent);
         break;
       case PN_SCOPE:
-        ((LexicalScopeNode*) this)->dump(indent);
+        ((LexicalScopeNode*) this)->dump(out, indent);
         break;
       default:
-        fprintf(stderr, "#<BAD NODE %p, kind=%u, arity=%u>",
+        out.printf("#<BAD NODE %p, kind=%u, arity=%u>",
                 (void*) this, unsigned(getKind()), unsigned(pn_arity));
         break;
     }
 }
 
 void
-NullaryNode::dump()
+NullaryNode::dump(GenericPrinter& out)
 {
     switch (getKind()) {
-      case PNK_TRUE:  fprintf(stderr, "#true");  break;
-      case PNK_FALSE: fprintf(stderr, "#false"); break;
-      case PNK_NULL:  fprintf(stderr, "#null");  break;
-      case PNK_RAW_UNDEFINED: fprintf(stderr, "#undefined"); break;
+      case PNK_TRUE:  out.put("#true");  break;
+      case PNK_FALSE: out.put("#false"); break;
+      case PNK_NULL:  out.put("#null");  break;
+      case PNK_RAW_UNDEFINED: out.put("#undefined"); break;
 
       case PNK_NUMBER: {
         ToCStringBuf cbuf;
         const char* cstr = NumberToCString(nullptr, &cbuf, pn_dval);
         if (!IsFinite(pn_dval))
-            fputc('#', stderr);
+            out.put("#");
         if (cstr)
-            fprintf(stderr, "%s", cstr);
+            out.printf("%s", cstr);
         else
-            fprintf(stderr, "%g", pn_dval);
+            out.printf("%g", pn_dval);
         break;
       }
 
       case PNK_STRING:
-        pn_atom->dumpCharsNoNewline();
+        pn_atom->dumpCharsNoNewline(out);
         break;
 
       default:
-        fprintf(stderr, "(%s)", parseNodeNames[getKind()]);
+        out.printf("(%s)", parseNodeNames[getKind()]);
     }
 }
 
 void
-UnaryNode::dump(int indent)
+UnaryNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s ", name);
+    out.printf("(%s ", name);
     indent += strlen(name) + 2;
-    DumpParseTree(pn_kid, indent);
-    fprintf(stderr, ")");
+    DumpParseTree(pn_kid, out, indent);
+    out.printf(")");
 }
 
 void
-BinaryNode::dump(int indent)
+BinaryNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s ", name);
+    out.printf("(%s ", name);
     indent += strlen(name) + 2;
-    DumpParseTree(pn_left, indent);
-    IndentNewLine(indent);
-    DumpParseTree(pn_right, indent);
-    fprintf(stderr, ")");
+    DumpParseTree(pn_left, out, indent);
+    IndentNewLine(out, indent);
+    DumpParseTree(pn_right, out, indent);
+    out.printf(")");
 }
 
 void
-TernaryNode::dump(int indent)
+TernaryNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s ", name);
+    out.printf("(%s ", name);
     indent += strlen(name) + 2;
-    DumpParseTree(pn_kid1, indent);
-    IndentNewLine(indent);
-    DumpParseTree(pn_kid2, indent);
-    IndentNewLine(indent);
-    DumpParseTree(pn_kid3, indent);
-    fprintf(stderr, ")");
+    DumpParseTree(pn_kid1, out, indent);
+    IndentNewLine(out, indent);
+    DumpParseTree(pn_kid2, out, indent);
+    IndentNewLine(out, indent);
+    DumpParseTree(pn_kid3, out, indent);
+    out.printf(")");
 }
 
 void
-CodeNode::dump(int indent)
+CodeNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s ", name);
+    out.printf("(%s ", name);
     indent += strlen(name) + 2;
-    DumpParseTree(pn_body, indent);
-    fprintf(stderr, ")");
+    DumpParseTree(pn_body, out, indent);
+    out.printf(")");
 }
 
 void
-ListNode::dump(int indent)
+ListNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s [", name);
+    out.printf("(%s [", name);
     if (pn_head != nullptr) {
         indent += strlen(name) + 3;
-        DumpParseTree(pn_head, indent);
+        DumpParseTree(pn_head, out, indent);
         ParseNode* pn = pn_head->pn_next;
         while (pn != nullptr) {
-            IndentNewLine(indent);
-            DumpParseTree(pn, indent);
+            IndentNewLine(out, indent);
+            DumpParseTree(pn, out, indent);
             pn = pn->pn_next;
         }
     }
-    fprintf(stderr, "])");
+    out.printf("])");
 }
 
 template <typename CharT>
 static void
-DumpName(const CharT* s, size_t len)
+DumpName(GenericPrinter& out, const CharT* s, size_t len)
 {
     if (len == 0)
-        fprintf(stderr, "#<zero-length name>");
+        out.put("#<zero-length name>");
 
     for (size_t i = 0; i < len; i++) {
         char16_t c = s[i];
         if (c > 32 && c < 127)
             fputc(c, stderr);
         else if (c <= 255)
-            fprintf(stderr, "\\x%02x", unsigned(c));
+            out.printf("\\x%02x", unsigned(c));
         else
-            fprintf(stderr, "\\u%04x", unsigned(c));
+            out.printf("\\u%04x", unsigned(c));
     }
 }
 
 void
-NameNode::dump(int indent)
+NameNode::dump(GenericPrinter& out, int indent)
 {
     if (isKind(PNK_NAME) || isKind(PNK_DOT)) {
         if (isKind(PNK_DOT))
-            fprintf(stderr, "(.");
+            out.put("(.");
 
         if (!pn_atom) {
-            fprintf(stderr, "#<null name>");
+            out.put("#<null name>");
         } else if (getOp() == JSOP_GETARG && pn_atom->length() == 0) {
             // Dump destructuring parameter.
-            fprintf(stderr, "(#<zero-length name> ");
-            DumpParseTree(expr(), indent + 21);
-            fputc(')', stderr);
+            out.put("(#<zero-length name> ");
+            DumpParseTree(expr(), out, indent + 21);
+            out.printf(")");
         } else {
             JS::AutoCheckCannotGC nogc;
             if (pn_atom->hasLatin1Chars())
-                DumpName(pn_atom->latin1Chars(nogc), pn_atom->length());
+                DumpName(out, pn_atom->latin1Chars(nogc), pn_atom->length());
             else
-                DumpName(pn_atom->twoByteChars(nogc), pn_atom->length());
+                DumpName(out, pn_atom->twoByteChars(nogc), pn_atom->length());
         }
 
         if (isKind(PNK_DOT)) {
-            fputc(' ', stderr);
+            out.putChar(' ');
             if (as<PropertyAccess>().isSuper())
-                fprintf(stderr, "super");
+                out.put("super");
             else
-                DumpParseTree(expr(), indent + 2);
-            fputc(')', stderr);
+                DumpParseTree(expr(), out, indent + 2);
+            out.printf(")");
         }
         return;
     }
 
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s ", name);
+    out.printf("(%s ", name);
     indent += strlen(name) + 2;
-    DumpParseTree(expr(), indent);
-    fprintf(stderr, ")");
+    DumpParseTree(expr(), out, indent);
+    out.printf(")");
 }
 
 void
-LexicalScopeNode::dump(int indent)
+LexicalScopeNode::dump(GenericPrinter& out, int indent)
 {
     const char* name = parseNodeNames[getKind()];
-    fprintf(stderr, "(%s [", name);
+    out.printf("(%s [", name);
     int nameIndent = indent + strlen(name) + 3;
     if (!isEmptyScope()) {
         LexicalScope::Data* bindings = scopeBindings();
         for (uint32_t i = 0; i < bindings->length; i++) {
             JSAtom* name = bindings->names[i].name();
             JS::AutoCheckCannotGC nogc;
             if (name->hasLatin1Chars())
-                DumpName(name->latin1Chars(nogc), name->length());
+                DumpName(out, name->latin1Chars(nogc), name->length());
             else
-                DumpName(name->twoByteChars(nogc), name->length());
+                DumpName(out, name->twoByteChars(nogc), name->length());
             if (i < bindings->length - 1)
-                IndentNewLine(nameIndent);
+                IndentNewLine(out, nameIndent);
         }
     }
-    fprintf(stderr, "]");
+    out.putChar(']');
     indent += 2;
-    IndentNewLine(indent);
-    DumpParseTree(scopeBody(), indent);
-    fprintf(stderr, ")");
+    IndentNewLine(out, indent);
+    DumpParseTree(scopeBody(), out, indent);
+    out.printf(")");
 }
 #endif
 
 ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink)
   : object(object),
     traceLink(traceLink),
     emitLink(nullptr)
 {
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -6,16 +6,17 @@
 
 #ifndef frontend_ParseNode_h
 #define frontend_ParseNode_h
 
 #include "mozilla/Attributes.h"
 
 #include "builtin/ModuleObject.h"
 #include "frontend/TokenStream.h"
+#include "vm/Printer.h"
 
 namespace js {
 namespace frontend {
 
 class ParseContext;
 class FullParseHandler;
 class FunctionBox;
 class ObjectBox;
@@ -813,18 +814,20 @@ class ParseNode
 
     template <class NodeType>
     inline const NodeType& as() const {
         MOZ_ASSERT(NodeType::test(*this));
         return *static_cast<const NodeType*>(this);
     }
 
 #ifdef DEBUG
+    // Debugger-friendly stderr printer.
     void dump();
-    void dump(int indent);
+    void dump(GenericPrinter& out);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct NullaryNode : public ParseNode
 {
     NullaryNode(ParseNodeKind kind, const TokenPos& pos)
       : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) {}
     NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
@@ -835,30 +838,30 @@ struct NullaryNode : public ParseNode
     // that nullary nodes shouldn't use -- bogus.
     NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, JSAtom* atom)
       : ParseNode(kind, op, PN_NULLARY, pos)
     {
         pn_atom = atom;
     }
 
 #ifdef DEBUG
-    void dump();
+    void dump(GenericPrinter& out);
 #endif
 };
 
 struct UnaryNode : public ParseNode
 {
     UnaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* kid)
       : ParseNode(kind, op, PN_UNARY, pos)
     {
         pn_kid = kid;
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct BinaryNode : public ParseNode
 {
     BinaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* left, ParseNode* right)
       : ParseNode(kind, op, PN_BINARY, pos)
     {
@@ -869,17 +872,17 @@ struct BinaryNode : public ParseNode
     BinaryNode(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right)
       : ParseNode(kind, op, PN_BINARY, TokenPos::box(left->pn_pos, right->pn_pos))
     {
         pn_left = left;
         pn_right = right;
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct TernaryNode : public ParseNode
 {
     TernaryNode(ParseNodeKind kind, JSOp op, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3)
       : ParseNode(kind, op, PN_TERNARY,
                   TokenPos((kid1 ? kid1 : kid2 ? kid2 : kid3)->pn_pos.begin,
@@ -895,17 +898,17 @@ struct TernaryNode : public ParseNode
       : ParseNode(kind, op, PN_TERNARY, pos)
     {
         pn_kid1 = kid1;
         pn_kid2 = kid2;
         pn_kid3 = kid3;
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct ListNode : public ParseNode
 {
     ListNode(ParseNodeKind kind, const TokenPos& pos)
       : ParseNode(kind, JSOP_NOP, PN_LIST, pos)
     {
@@ -924,17 +927,17 @@ struct ListNode : public ParseNode
         initList(kid);
     }
 
     static bool test(const ParseNode& node) {
         return node.isArity(PN_LIST);
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct CodeNode : public ParseNode
 {
     CodeNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
       : ParseNode(kind, op, PN_CODE, pos)
     {
@@ -944,31 +947,31 @@ struct CodeNode : public ParseNode
                    op == JSOP_LAMBDA_ARROW || // arrow function
                    op == JSOP_LAMBDA); // expression, method, comprehension, accessor, &c.
         MOZ_ASSERT(!pn_body);
         MOZ_ASSERT(!pn_objbox);
     }
 
   public:
 #ifdef DEBUG
-    void dump(int indent);
+  void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct NameNode : public ParseNode
 {
     NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, const TokenPos& pos)
       : ParseNode(kind, op, PN_NAME, pos)
     {
         pn_atom = atom;
         pn_expr = nullptr;
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 struct LexicalScopeNode : public ParseNode
 {
     LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body)
       : ParseNode(PNK_LEXICALSCOPE, JSOP_NOP, PN_SCOPE, body->pn_pos)
     {
@@ -976,17 +979,17 @@ struct LexicalScopeNode : public ParseNo
         pn_u.scope.body = body;
     }
 
     static bool test(const ParseNode& node) {
         return node.isKind(PNK_LEXICALSCOPE);
     }
 
 #ifdef DEBUG
-    void dump(int indent);
+    void dump(GenericPrinter& out, int indent);
 #endif
 };
 
 class LabeledStatement : public ParseNode
 {
   public:
     LabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin)
       : ParseNode(PNK_LABEL, JSOP_NOP, PN_NAME, TokenPos(begin, stmt->pn_pos.end))
@@ -1347,17 +1350,17 @@ struct ClassNode : public TernaryNode {
     }
     Handle<LexicalScope::Data*> scopeBindings() const {
         MOZ_ASSERT(pn_kid3->is<LexicalScopeNode>());
         return pn_kid3->scopeBindings();
     }
 };
 
 #ifdef DEBUG
-void DumpParseTree(ParseNode* pn, int indent = 0);
+void DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent = 0);
 #endif
 
 class ParseNodeAllocator
 {
   public:
     explicit ParseNodeAllocator(JSContext* cx, LifoAlloc& alloc)
       : cx(cx), alloc(alloc), freelist(nullptr)
     {}
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -25,16 +25,18 @@
 
 #include "ds/BitArray.h"
 #include "gc/Memory.h"
 #include "js/GCAPI.h"
 #include "js/HeapAPI.h"
 #include "js/RootingAPI.h"
 #include "js/TracingAPI.h"
 
+#include "vm/Printer.h"
+
 struct JSRuntime;
 
 namespace js {
 
 class AutoLockGC;
 class FreeOp;
 
 extern bool
@@ -273,17 +275,17 @@ struct Cell
     inline StoreBuffer* storeBuffer() const;
 
     inline JS::TraceKind getTraceKind() const;
 
     static MOZ_ALWAYS_INLINE bool needWriteBarrierPre(JS::Zone* zone);
 
 #ifdef DEBUG
     inline bool isAligned() const;
-    void dump(FILE* fp) const;
+    void dump(js::GenericPrinter& out) const;
     void dump() const;
 #endif
 
   protected:
     inline uintptr_t address() const;
     inline Chunk* chunk() const;
 } JS_HAZ_GC_THING;
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -658,44 +658,113 @@ JS_CloneObject(JSContext* cx, HandleObje
     // |obj| might be in a different compartment.
     assertSameCompartment(cx, protoArg);
     Rooted<TaggedProto> proto(cx, TaggedProto(protoArg.get()));
     return CloneObject(cx, obj, proto);
 }
 
 #ifdef DEBUG
 
+// We don't want jsfriendapi.h to depend on GenericPrinter,
+// so these functions are declared directly in the cpp.
+
+namespace js {
+
+void
+DumpString(JSString* str, js::GenericPrinter& out);
+
+void
+DumpAtom(JSAtom* atom, js::GenericPrinter& out);
+
+void
+DumpObject(JSObject* obj, js::GenericPrinter& out);
+
+void
+DumpChars(const char16_t* s, size_t n, js::GenericPrinter& out);
+
+void
+DumpValue(const JS::Value& val, js::GenericPrinter& out);
+
+void
+DumpId(jsid id, js::GenericPrinter& out);
+
+void
+DumpInterpreterFrame(JSContext* cx, js::GenericPrinter& out, InterpreterFrame* start = nullptr);
+
+} // namespace js
+
+JS_FRIEND_API(void)
+js::DumpString(JSString* str, js::GenericPrinter& out)
+{
+    str->dump(out);
+}
+
+JS_FRIEND_API(void)
+js::DumpAtom(JSAtom* atom, js::GenericPrinter& out)
+{
+    atom->dump(out);
+}
+
+JS_FRIEND_API(void)
+js::DumpChars(const char16_t* s, size_t n, js::GenericPrinter& out)
+{
+    out.printf("char16_t * (%p) = ", (void*) s);
+    JSString::dumpChars(s, n, out);
+    out.putChar('\n');
+}
+
+JS_FRIEND_API(void)
+js::DumpObject(JSObject* obj, js::GenericPrinter& out)
+{
+    if (!obj) {
+        out.printf("NULL\n");
+        return;
+    }
+    obj->dump(out);
+}
+
 JS_FRIEND_API(void)
 js::DumpString(JSString* str, FILE* fp)
 {
-    str->dump(fp);
+    Fprinter out(fp);
+    js::DumpString(str, out);
 }
 
 JS_FRIEND_API(void)
 js::DumpAtom(JSAtom* atom, FILE* fp)
 {
-    atom->dump(fp);
+    Fprinter out(fp);
+    js::DumpAtom(atom, out);
 }
 
 JS_FRIEND_API(void)
 js::DumpChars(const char16_t* s, size_t n, FILE* fp)
 {
-    fprintf(fp, "char16_t * (%p) = ", (void*) s);
-    JSString::dumpChars(s, n, fp);
-    fputc('\n', fp);
+    Fprinter out(fp);
+    js::DumpChars(s, n, out);
 }
 
 JS_FRIEND_API(void)
 js::DumpObject(JSObject* obj, FILE* fp)
 {
-    if (!obj) {
-        fprintf(fp, "NULL\n");
-        return;
-    }
-    obj->dump(fp);
+    Fprinter out(fp);
+    js::DumpObject(obj, out);
+}
+
+JS_FRIEND_API(void)
+js::DumpId(jsid id, FILE* fp)
+{
+    Fprinter out(fp);
+    js::DumpId(id, out);
+}
+
+JS_FRIEND_API(void)
+js::DumpValue(const JS::Value& val, FILE* fp) {
+    Fprinter out(fp);
+    js::DumpValue(val, out);
 }
 
 JS_FRIEND_API(void)
 js::DumpString(JSString* str) {
     DumpString(str, stderr);
 }
 JS_FRIEND_API(void)
 js::DumpAtom(JSAtom* atom) {
@@ -715,17 +784,18 @@ js::DumpValue(const JS::Value& val) {
 }
 JS_FRIEND_API(void)
 js::DumpId(jsid id) {
     DumpId(id, stderr);
 }
 JS_FRIEND_API(void)
 js::DumpInterpreterFrame(JSContext* cx, InterpreterFrame* start)
 {
-    DumpInterpreterFrame(cx, stderr, start);
+    Fprinter out(stderr);
+    DumpInterpreterFrame(cx, out, start);
 }
 JS_FRIEND_API(bool)
 js::DumpPC(JSContext* cx) {
     return DumpPC(cx, stdout);
 }
 JS_FRIEND_API(bool)
 js::DumpScript(JSContext* cx, JSScript* scriptArg)
 {
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -17,16 +17,18 @@
 #include "jsbytecode.h"
 #include "jspubtd.h"
 
 #include "js/CallArgs.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Class.h"
 #include "js/Utility.h"
 
+#include "vm/Printer.h"
+
 #if JS_STACK_GROWTH_DIRECTION > 0
 # define JS_CHECK_STACK_SIZE(limit, sp) (MOZ_LIKELY((uintptr_t)(sp) < (limit)))
 #else
 # define JS_CHECK_STACK_SIZE(limit, sp) (MOZ_LIKELY((uintptr_t)(sp) > (limit)))
 #endif
 
 class JSAtom;
 struct JSErrorFormatString;
@@ -292,16 +294,19 @@ extern JS_FRIEND_API(void) DumpValue(con
 extern JS_FRIEND_API(void) DumpId(jsid id);
 extern JS_FRIEND_API(void) DumpInterpreterFrame(JSContext* cx, InterpreterFrame* start = nullptr);
 extern JS_FRIEND_API(bool) DumpPC(JSContext* cx);
 extern JS_FRIEND_API(bool) DumpScript(JSContext* cx, JSScript* scriptArg);
 
 #endif
 
 extern JS_FRIEND_API(void)
+DumpBacktrace(JSContext* cx, js::GenericPrinter& out);
+
+extern JS_FRIEND_API(void)
 DumpBacktrace(JSContext* cx, FILE* fp);
 
 extern JS_FRIEND_API(void)
 DumpBacktrace(JSContext* cx);
 
 } // namespace js
 
 namespace JS {
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -229,16 +229,17 @@
 #include "gc/Policy.h"
 #include "jit/BaselineJIT.h"
 #include "jit/IonCode.h"
 #include "jit/JitcodeMap.h"
 #include "js/SliceBudget.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/Debugger.h"
 #include "vm/GeckoProfiler.h"
+#include "vm/Printer.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
 #include "vm/Symbol.h"
 #include "vm/Time.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
 
@@ -8735,41 +8736,42 @@ AutoEmptyNursery::AutoEmptyNursery(JSCon
     checkCondition(cx);
 }
 
 } /* namespace gc */
 } /* namespace js */
 
 #ifdef DEBUG
 void
-js::gc::Cell::dump(FILE* fp) const
+js::gc::Cell::dump(js::GenericPrinter& out) const
 {
     switch (getTraceKind()) {
       case JS::TraceKind::Object:
-        reinterpret_cast<const JSObject*>(this)->dump(fp);
+        reinterpret_cast<const JSObject*>(this)->dump(out);
         break;
 
       case JS::TraceKind::String:
-          js::DumpString(reinterpret_cast<JSString*>(const_cast<Cell*>(this)), fp);
+          js::DumpString(reinterpret_cast<JSString*>(const_cast<Cell*>(this)), out);
         break;
 
       case JS::TraceKind::Shape:
-        reinterpret_cast<const Shape*>(this)->dump(fp);
+        reinterpret_cast<const Shape*>(this)->dump(out);
         break;
 
       default:
-        fprintf(fp, "%s(%p)\n", JS::GCTraceKindToAscii(getTraceKind()), (void*) this);
+        out.printf("%s(%p)\n", JS::GCTraceKindToAscii(getTraceKind()), (void*) this);
     }
 }
 
 // For use in a debugger.
 void
 js::gc::Cell::dump() const
 {
-    dump(stderr);
+    js::Fprinter out(stderr);
+    dump(out);
 }
 #endif
 
 static inline bool
 CanCheckGrayBits(const Cell* cell)
 {
     MOZ_ASSERT(cell);
     if (!cell->isTenured())
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3356,345 +3356,369 @@ GetObjectSlotNameFunctor::operator()(JS:
 
 /*
  * Routines to print out values during debugging.  These are FRIEND_API to help
  * the debugger find them and to support temporarily hacking js::Dump* calls
  * into other code.
  */
 
 static void
-dumpValue(const Value& v, FILE* fp)
+dumpValue(const Value& v, js::GenericPrinter& out)
 {
     if (v.isNull())
-        fprintf(fp, "null");
+        out.put("null");
     else if (v.isUndefined())
-        fprintf(fp, "undefined");
+        out.put("undefined");
     else if (v.isInt32())
-        fprintf(fp, "%d", v.toInt32());
+        out.printf("%d", v.toInt32());
     else if (v.isDouble())
-        fprintf(fp, "%g", v.toDouble());
+        out.printf("%g", v.toDouble());
     else if (v.isString())
-        v.toString()->dump(fp);
+        v.toString()->dump(out);
     else if (v.isSymbol())
-        v.toSymbol()->dump(fp);
+        v.toSymbol()->dump(out);
     else if (v.isObject() && v.toObject().is<JSFunction>()) {
         JSFunction* fun = &v.toObject().as<JSFunction>();
         if (fun->displayAtom()) {
-            fputs("<function ", fp);
-            FileEscapedString(fp, fun->displayAtom(), 0);
+            out.put("<function ");
+            EscapedStringPrinter(out, fun->displayAtom(), 0);
         } else {
-            fputs("<unnamed function", fp);
+            out.put("<unnamed function");
         }
         if (fun->hasScript()) {
             JSScript* script = fun->nonLazyScript();
-            fprintf(fp, " (%s:%zu)",
+            out.printf(" (%s:%zu)",
                     script->filename() ? script->filename() : "", script->lineno());
         }
-        fprintf(fp, " at %p>", (void*) fun);
+        out.printf(" at %p>", (void*) fun);
     } else if (v.isObject()) {
         JSObject* obj = &v.toObject();
         const Class* clasp = obj->getClass();
-        fprintf(fp, "<%s%s at %p>",
+        out.printf("<%s%s at %p>",
                 clasp->name,
                 (clasp == &PlainObject::class_) ? "" : " object",
                 (void*) obj);
     } else if (v.isBoolean()) {
         if (v.toBoolean())
-            fprintf(fp, "true");
+            out.put("true");
         else
-            fprintf(fp, "false");
+            out.put("false");
     } else if (v.isMagic()) {
-        fprintf(fp, "<invalid");
+        out.put("<invalid");
 #ifdef DEBUG
         switch (v.whyMagic()) {
-          case JS_ELEMENTS_HOLE:     fprintf(fp, " elements hole");      break;
-          case JS_NO_ITER_VALUE:     fprintf(fp, " no iter value");      break;
-          case JS_GENERATOR_CLOSING: fprintf(fp, " generator closing");  break;
-          case JS_OPTIMIZED_OUT:     fprintf(fp, " optimized out");      break;
-          default:                   fprintf(fp, " ?!");                 break;
+          case JS_ELEMENTS_HOLE:     out.put(" elements hole");      break;
+          case JS_NO_ITER_VALUE:     out.put(" no iter value");      break;
+          case JS_GENERATOR_CLOSING: out.put(" generator closing");  break;
+          case JS_OPTIMIZED_OUT:     out.put(" optimized out");      break;
+          default:                   out.put(" ?!");                 break;
         }
 #endif
-        fprintf(fp, ">");
+        out.putChar('>');
     } else {
-        fprintf(fp, "unexpected value");
+        out.put("unexpected value");
     }
 }
 
+namespace js {
+
+// We don't want jsfriendapi.h to depend on GenericPrinter,
+// so these functions are declared directly in the cpp.
+
+void
+DumpValue(const JS::Value& val, js::GenericPrinter& out);
+
+void
+DumpId(jsid id, js::GenericPrinter& out);
+
+void
+DumpInterpreterFrame(JSContext* cx, js::GenericPrinter& out, InterpreterFrame* start = nullptr);
+
+} // namespace js
+
+JS_FRIEND_API(void)
+js::DumpValue(const Value& val, js::GenericPrinter& out)
+{
+    dumpValue(val, out);
+    out.putChar('\n');
+}
+
 JS_FRIEND_API(void)
-js::DumpValue(const Value& val, FILE* fp)
+js::DumpId(jsid id, js::GenericPrinter& out)
 {
-    dumpValue(val, fp);
-    fputc('\n', fp);
-}
-
-JS_FRIEND_API(void)
-js::DumpId(jsid id, FILE* fp)
-{
-    fprintf(fp, "jsid %p = ", (void*) JSID_BITS(id));
-    dumpValue(IdToValue(id), fp);
-    fputc('\n', fp);
+    out.printf("jsid %p = ", (void*) JSID_BITS(id));
+    dumpValue(IdToValue(id), out);
+    out.putChar('\n');
 }
 
 static void
-DumpProperty(const NativeObject* obj, Shape& shape, FILE* fp)
+DumpProperty(const NativeObject* obj, Shape& shape, js::GenericPrinter& out)
 {
     jsid id = shape.propid();
     uint8_t attrs = shape.attributes();
 
-    fprintf(fp, "    ((js::Shape*) %p) ", (void*) &shape);
-    if (attrs & JSPROP_ENUMERATE) fprintf(fp, "enumerate ");
-    if (attrs & JSPROP_READONLY) fprintf(fp, "readonly ");
-    if (attrs & JSPROP_PERMANENT) fprintf(fp, "permanent ");
-    if (attrs & JSPROP_SHARED) fprintf(fp, "shared ");
+    out.printf("    ((js::Shape*) %p) ", (void*) &shape);
+    if (attrs & JSPROP_ENUMERATE) out.put("enumerate ");
+    if (attrs & JSPROP_READONLY) out.put("readonly ");
+    if (attrs & JSPROP_PERMANENT) out.put("permanent ");
+    if (attrs & JSPROP_SHARED) out.put("shared ");
 
     if (shape.hasGetterValue())
-        fprintf(fp, "getterValue=%p ", (void*) shape.getterObject());
+        out.printf("getterValue=%p ", (void*) shape.getterObject());
     else if (!shape.hasDefaultGetter())
-        fprintf(fp, "getterOp=%p ", JS_FUNC_TO_DATA_PTR(void*, shape.getterOp()));
+        out.printf("getterOp=%p ", JS_FUNC_TO_DATA_PTR(void*, shape.getterOp()));
 
     if (shape.hasSetterValue())
-        fprintf(fp, "setterValue=%p ", (void*) shape.setterObject());
+        out.printf("setterValue=%p ", (void*) shape.setterObject());
     else if (!shape.hasDefaultSetter())
-        fprintf(fp, "setterOp=%p ", JS_FUNC_TO_DATA_PTR(void*, shape.setterOp()));
+        out.printf("setterOp=%p ", JS_FUNC_TO_DATA_PTR(void*, shape.setterOp()));
 
     if (JSID_IS_ATOM(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id))
-        dumpValue(js::IdToValue(id), fp);
+        dumpValue(js::IdToValue(id), out);
     else
-        fprintf(fp, "unknown jsid %p", (void*) JSID_BITS(id));
+        out.printf("unknown jsid %p", (void*) JSID_BITS(id));
 
     uint32_t slot = shape.hasSlot() ? shape.maybeSlot() : SHAPE_INVALID_SLOT;
-    fprintf(fp, ": slot %d", slot);
+    out.printf(": slot %d", slot);
     if (shape.hasSlot()) {
-        fprintf(fp, " = ");
-        dumpValue(obj->getSlot(slot), fp);
+        out.put(" = ");
+        dumpValue(obj->getSlot(slot), out);
     } else if (slot != SHAPE_INVALID_SLOT) {
-        fprintf(fp, " (INVALID!)");
+        out.printf(" (INVALID!)");
     }
-    fprintf(fp, "\n");
+    out.putChar('\n');
 }
 
 bool
 JSObject::uninlinedIsProxy() const
 {
     return is<ProxyObject>();
 }
 
 bool
 JSObject::uninlinedNonProxyIsExtensible() const
 {
     return nonProxyIsExtensible();
 }
 
 void
-JSObject::dump(FILE* fp) const
+JSObject::dump(js::GenericPrinter& out) const
 {
     const JSObject* obj = this;
     JSObject* globalObj = &global();
-    fprintf(fp, "object %p from global %p [%s]\n", (void*) obj,
+    out.printf("object %p from global %p [%s]\n", (void*) obj,
             (void*) globalObj, globalObj->getClass()->name);
     const Class* clasp = obj->getClass();
-    fprintf(fp, "class %p %s\n", (const void*)clasp, clasp->name);
+    out.printf("class %p %s\n", (const void*)clasp, clasp->name);
 
     if (obj->hasLazyGroup()) {
-        fprintf(fp, "lazy group\n");
+        out.put("lazy group\n");
     } else {
         const ObjectGroup* group = obj->group();
-        fprintf(fp, "group %p\n", (const void*)group);
+        out.printf("group %p\n", (const void*)group);
     }
 
-    fprintf(fp, "flags:");
-    if (obj->isDelegate()) fprintf(fp, " delegate");
-    if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(fp, " not_extensible");
-    if (obj->isIndexed()) fprintf(fp, " indexed");
-    if (obj->maybeHasInterestingSymbolProperty()) fprintf(fp, " maybe_has_interesting_symbol");
-    if (obj->isBoundFunction()) fprintf(fp, " bound_function");
-    if (obj->isQualifiedVarObj()) fprintf(fp, " varobj");
-    if (obj->isUnqualifiedVarObj()) fprintf(fp, " unqualified_varobj");
-    if (obj->watched()) fprintf(fp, " watched");
-    if (obj->isIteratedSingleton()) fprintf(fp, " iterated_singleton");
-    if (obj->isNewGroupUnknown()) fprintf(fp, " new_type_unknown");
-    if (obj->hasUncacheableProto()) fprintf(fp, " has_uncacheable_proto");
-    if (obj->hadElementsAccess()) fprintf(fp, " had_elements_access");
-    if (obj->wasNewScriptCleared()) fprintf(fp, " new_script_cleared");
+    out.put("flags:");
+    if (obj->isDelegate()) out.put(" delegate");
+    if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) out.put(" not_extensible");
+    if (obj->isIndexed()) out.put(" indexed");
+    if (obj->maybeHasInterestingSymbolProperty()) out.put(" maybe_has_interesting_symbol");
+    if (obj->isBoundFunction()) out.put(" bound_function");
+    if (obj->isQualifiedVarObj()) out.put(" varobj");
+    if (obj->isUnqualifiedVarObj()) out.put(" unqualified_varobj");
+    if (obj->watched()) out.put(" watched");
+    if (obj->isIteratedSingleton()) out.put(" iterated_singleton");
+    if (obj->isNewGroupUnknown()) out.put(" new_type_unknown");
+    if (obj->hasUncacheableProto()) out.put(" has_uncacheable_proto");
+    if (obj->hadElementsAccess()) out.put(" had_elements_access");
+    if (obj->wasNewScriptCleared()) out.put(" new_script_cleared");
     if (obj->hasStaticPrototype() && obj->staticPrototypeIsImmutable())
-        fprintf(fp, " immutable_prototype");
+        out.put(" immutable_prototype");
 
     if (obj->isNative()) {
         const NativeObject* nobj = &obj->as<NativeObject>();
         if (nobj->inDictionaryMode())
-            fprintf(fp, " inDictionaryMode");
+            out.put(" inDictionaryMode");
         if (nobj->hasShapeTable())
-            fprintf(fp, " hasShapeTable");
+            out.put(" hasShapeTable");
     }
-    fprintf(fp, "\n");
+    out.putChar('\n');
 
     if (obj->isNative()) {
         const NativeObject* nobj = &obj->as<NativeObject>();
         uint32_t slots = nobj->getDenseInitializedLength();
         if (slots) {
-            fprintf(fp, "elements\n");
+            out.put("elements\n");
             for (uint32_t i = 0; i < slots; i++) {
-                fprintf(fp, " %3d: ", i);
-                dumpValue(nobj->getDenseElement(i), fp);
-                fprintf(fp, "\n");
-                fflush(fp);
+                out.printf(" %3d: ", i);
+                dumpValue(nobj->getDenseElement(i), out);
+                out.putChar('\n');
+                out.flush();
             }
         }
     }
 
-    fprintf(fp, "proto ");
+    out.put("proto ");
     TaggedProto proto = obj->taggedProto();
     if (proto.isDynamic())
-        fprintf(fp, "<dynamic>");
+        out.put("<dynamic>");
     else
-        dumpValue(ObjectOrNullValue(proto.toObjectOrNull()), fp);
-    fputc('\n', fp);
+        dumpValue(ObjectOrNullValue(proto.toObjectOrNull()), out);
+    out.putChar('\n');
 
     if (clasp->flags & JSCLASS_HAS_PRIVATE)
-        fprintf(fp, "private %p\n", obj->as<NativeObject>().getPrivate());
+        out.printf("private %p\n", obj->as<NativeObject>().getPrivate());
 
     if (!obj->isNative())
-        fprintf(fp, "not native\n");
+        out.put("not native\n");
 
     uint32_t reservedEnd = JSCLASS_RESERVED_SLOTS(clasp);
     uint32_t slots = obj->isNative() ? obj->as<NativeObject>().slotSpan() : 0;
     uint32_t stop = obj->isNative() ? reservedEnd : slots;
     if (stop > 0)
-        fprintf(fp, obj->isNative() ? "reserved slots:\n" : "slots:\n");
+        out.printf(obj->isNative() ? "reserved slots:\n" : "slots:\n");
     for (uint32_t i = 0; i < stop; i++) {
-        fprintf(fp, " %3d ", i);
+        out.printf(" %3d ", i);
         if (i < reservedEnd)
-            fprintf(fp, "(reserved) ");
-        fprintf(fp, "= ");
-        dumpValue(obj->as<NativeObject>().getSlot(i), fp);
-        fputc('\n', fp);
+            out.printf("(reserved) ");
+        out.put("= ");
+        dumpValue(obj->as<NativeObject>().getSlot(i), out);
+        out.putChar('\n');
     }
 
     if (obj->isNative()) {
-        fprintf(fp, "properties:\n");
+        out.put("properties:\n");
         Vector<Shape*, 8, SystemAllocPolicy> props;
         for (Shape::Range<NoGC> r(obj->as<NativeObject>().lastProperty()); !r.empty(); r.popFront()) {
             if (!props.append(&r.front())) {
-                fprintf(fp, "(OOM while appending properties)\n");
+                out.printf("(OOM while appending properties)\n");
                 break;
             }
         }
         for (size_t i = props.length(); i-- != 0;)
-            DumpProperty(&obj->as<NativeObject>(), *props[i], fp);
+            DumpProperty(&obj->as<NativeObject>(), *props[i], out);
     }
-    fputc('\n', fp);
+    out.putChar('\n');
 }
 
 // For debuggers.
 void
 JSObject::dump() const
 {
-    dump(stderr);
+    Fprinter out(stderr);
+    dump(out);
 }
 
 static void
-MaybeDumpScope(Scope* scope, FILE* fp)
+MaybeDumpScope(Scope* scope, js::GenericPrinter& out)
 {
     if (scope) {
-        fprintf(fp, "  scope: %s\n", ScopeKindString(scope->kind()));
+        out.printf("  scope: %s\n", ScopeKindString(scope->kind()));
         for (BindingIter bi(scope); bi; bi++) {
-            fprintf(fp, "    ");
-            dumpValue(StringValue(bi.name()), fp);
-            fputc('\n', fp);
+            out.put("    ");
+            dumpValue(StringValue(bi.name()), out);
+            out.putChar('\n');
         }
     }
 }
 
 static void
-MaybeDumpValue(const char* name, const Value& v, FILE* fp)
+MaybeDumpValue(const char* name, const Value& v, js::GenericPrinter& out)
 {
     if (!v.isNull()) {
-        fprintf(fp, "  %s: ", name);
-        dumpValue(v, fp);
-        fputc('\n', fp);
+        out.printf("  %s: ", name);
+        dumpValue(v, out);
+        out.putChar('\n');
     }
 }
 
 JS_FRIEND_API(void)
-js::DumpInterpreterFrame(JSContext* cx, FILE* fp, InterpreterFrame* start)
+js::DumpInterpreterFrame(JSContext* cx, js::GenericPrinter& out, InterpreterFrame* start)
 {
     /* This should only called during live debugging. */
     ScriptFrameIter i(cx);
     if (!start) {
         if (i.done()) {
-            fprintf(fp, "no stack for cx = %p\n", (void*) cx);
+            out.printf("no stack for cx = %p\n", (void*) cx);
             return;
         }
     } else {
         while (!i.done() && !i.isJSJit() && i.interpFrame() != start)
             ++i;
 
         if (i.done()) {
-            fprintf(fp, "fp = %p not found in cx = %p\n",
+            out.printf("fp = %p not found in cx = %p\n",
                     (void*)start, (void*)cx);
             return;
         }
     }
 
     for (; !i.done(); ++i) {
         if (i.isJSJit())
-            fprintf(fp, "JIT frame\n");
+            out.put("JIT frame\n");
         else
-            fprintf(fp, "InterpreterFrame at %p\n", (void*) i.interpFrame());
+            out.printf("InterpreterFrame at %p\n", (void*) i.interpFrame());
 
         if (i.isFunctionFrame()) {
-            fprintf(fp, "callee fun: ");
+            out.put("callee fun: ");
             RootedValue v(cx);
             JSObject* fun = i.callee(cx);
             v.setObject(*fun);
-            dumpValue(v, fp);
+            dumpValue(v, out);
         } else {
-            fprintf(fp, "global or eval frame, no callee");
+            out.put("global or eval frame, no callee");
         }
-        fputc('\n', fp);
-
-        fprintf(fp, "file %s line %zu\n",
+        out.putChar('\n');
+
+        out.printf("file %s line %zu\n",
                 i.script()->filename(), i.script()->lineno());
 
         if (jsbytecode* pc = i.pc()) {
-            fprintf(fp, "  pc = %p\n", pc);
-            fprintf(fp, "  current op: %s\n", CodeName[*pc]);
-            MaybeDumpScope(i.script()->lookupScope(pc), fp);
+            out.printf("  pc = %p\n", pc);
+            out.printf("  current op: %s\n", CodeName[*pc]);
+            MaybeDumpScope(i.script()->lookupScope(pc), out);
         }
         if (i.isFunctionFrame())
-            MaybeDumpValue("this", i.thisArgument(cx), fp);
+            MaybeDumpValue("this", i.thisArgument(cx), out);
         if (!i.isJSJit()) {
-            fprintf(fp, "  rval: ");
-            dumpValue(i.interpFrame()->returnValue(), fp);
-            fputc('\n', fp);
+            out.put("  rval: ");
+            dumpValue(i.interpFrame()->returnValue(), out);
+            out.putChar('\n');
         }
 
-        fprintf(fp, "  flags:");
+        out.put("  flags:");
         if (i.isConstructing())
-            fprintf(fp, " constructing");
+            out.put(" constructing");
         if (!i.isJSJit() && i.interpFrame()->isDebuggerEvalFrame())
-            fprintf(fp, " debugger eval");
+            out.put(" debugger eval");
         if (i.isEvalFrame())
-            fprintf(fp, " eval");
-        fputc('\n', fp);
-
-        fprintf(fp, "  envChain: (JSObject*) %p\n", (void*) i.environmentChain(cx));
-
-        fputc('\n', fp);
+            out.put(" eval");
+        out.putChar('\n');
+
+        out.printf("  envChain: (JSObject*) %p\n", (void*) i.environmentChain(cx));
+
+        out.putChar('\n');
     }
 }
 
 #endif /* DEBUG */
 
 JS_FRIEND_API(void)
 js::DumpBacktrace(JSContext* cx, FILE* fp)
 {
+    Fprinter out(fp);
+    js::DumpBacktrace(cx, out);
+}
+
+JS_FRIEND_API(void)
+js::DumpBacktrace(JSContext* cx, js::GenericPrinter& out)
+{
     Sprinter sprinter(cx, false);
     if (!sprinter.init()) {
-        fprintf(fp, "js::DumpBacktrace: OOM\n");
+        out.put("js::DumpBacktrace: OOM\n");
         return;
     }
     size_t depth = 0;
     for (AllFramesIter i(cx); !i.done(); ++i, ++depth) {
         const char* filename;
         unsigned line;
         if (i.hasScript()) {
             filename = JS_GetScriptFilename(i.script());
@@ -3715,17 +3739,17 @@ js::DumpBacktrace(JSContext* cx, FILE* f
 
         if (i.hasScript()) {
             sprinter.printf(" (%p @ %zu)\n",
                             i.script(), i.script()->pcToOffset(i.pc()));
         } else {
             sprinter.printf(" (%p)\n", i.pc());
         }
     }
-    fprintf(fp, "%s", sprinter.string());
+    out.printf("%s", sprinter.string());
 #ifdef XP_WIN32
     if (IsDebuggerPresent())
         OutputDebugStringA(sprinter.string());
 #endif
 }
 
 JS_FRIEND_API(void)
 js::DumpBacktrace(JSContext* cx)
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -19,16 +19,17 @@
 #include "mozilla/MemoryReporting.h"
 
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "js/Conversions.h"
 #include "js/GCAPI.h"
 #include "js/GCVector.h"
 #include "js/HeapAPI.h"
+#include "vm/Printer.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
 #include "vm/Xdr.h"
 
 namespace JS {
 struct ClassInfo;
 } // namespace JS
 
@@ -578,17 +579,17 @@ class JSObject : public js::gc::Cell
 
     template <class T>
     const T& as() const {
         MOZ_ASSERT(this->is<T>());
         return *static_cast<const T*>(this);
     }
 
 #ifdef DEBUG
-    void dump(FILE* fp) const;
+    void dump(js::GenericPrinter& fp) const;
     void dump() const;
 #endif
 
     /* JIT Accessors */
 
     static size_t offsetOfGroup() { return offsetof(JSObject, group_); }
 
     // Maximum size in bytes of a JSObject.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -88,16 +88,17 @@
 #include "vm/ArgumentsObject.h"
 #include "vm/AsyncFunction.h"
 #include "vm/AsyncIteration.h"
 #include "vm/Compression.h"
 #include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/Monitor.h"
 #include "vm/MutexIDs.h"
+#include "vm/Printer.h"
 #include "vm/Shape.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/StringBuffer.h"
 #include "vm/Time.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 #include "wasm/WasmJS.h"
 
@@ -4304,18 +4305,19 @@ Parse(JSContext* cx, unsigned argc, Valu
                                               nullptr);
     if (!parser.checkOptions())
         return false;
 
     ParseNode* pn = parser.parse();
     if (!pn)
         return false;
 #ifdef DEBUG
-    DumpParseTree(pn);
-    fputc('\n', stderr);
+    js::Fprinter out(stderr);
+    DumpParseTree(pn, out);
+    out.putChar('\n');
 #endif
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 SyntaxParse(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/vm/Printer.h
+++ b/js/src/vm/Printer.h
@@ -33,20 +33,24 @@ class GenericPrinter
     bool                  hadOOM_;     // whether reportOutOfMemory() has been called.
 
     GenericPrinter();
 
   public:
     // Puts |len| characters from |s| at the current position and
     // return true on success, false on failure.
     virtual bool put(const char* s, size_t len) = 0;
+    virtual void flush() { /* Do nothing */ }
 
     inline bool put(const char* s) {
         return put(s, strlen(s));
     }
+    inline bool putChar(const char c) {
+        return put(&c, 1);
+    }
 
     // Prints a formatted string into the buffer.
     bool printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
     bool vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0);
 
     // Report that a string operation failed to get the memory it requested.
     virtual void reportOutOfMemory();
 
@@ -141,17 +145,17 @@ class Fprinter final : public GenericPri
     ~Fprinter();
 
     // Initialize this printer, returns false on error.
     MOZ_MUST_USE bool init(const char* path);
     void init(FILE* fp);
     bool isInitialized() const {
         return file_ != nullptr;
     }
-    void flush();
+    void flush() override;
     void finish();
 
     // Puts |len| characters from |s| at the current position and
     // return true on success, false on failure.
     virtual bool put(const char* s, size_t len) override;
     using GenericPrinter::put; // pick up |inline bool put(const char* s);|
 };
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -39,16 +39,17 @@
 #include "gc/Policy.h"
 #include "jit/AtomicOperations.h"
 #include "jit/InlinableNatives.h"
 #include "js/CharacterEncoding.h"
 #include "js/Date.h"
 #include "vm/Compression.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Interpreter.h"
+#include "vm/Printer.h"
 #include "vm/RegExpObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
 #include "jsatominlines.h"
 #include "jsfuninlines.h"
@@ -406,19 +407,20 @@ static bool
 intrinsic_AssertionFailed(JSContext* cx, unsigned argc, Value* vp)
 {
 #ifdef DEBUG
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() > 0) {
         // try to dump the informative string
         JSString* str = ToString<CanGC>(cx, args[0]);
         if (str) {
-            fprintf(stderr, "Self-hosted JavaScript assertion info: ");
-            str->dumpCharsNoNewline();
-            fputc('\n', stderr);
+            js::Fprinter out(stderr);
+            out.put("Self-hosted JavaScript assertion info: ");
+            str->dumpCharsNoNewline(out);
+            out.putChar('\n');
         }
     }
 #endif
     MOZ_ASSERT(false);
     return false;
 }
 
 /**
@@ -426,20 +428,21 @@ intrinsic_AssertionFailed(JSContext* cx,
  */
 static bool
 intrinsic_DumpMessage(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 #ifdef DEBUG
     if (args.length() > 0) {
         // try to dump the informative string
+        js::Fprinter out(stderr);
         JSString* str = ToString<CanGC>(cx, args[0]);
         if (str) {
-            str->dumpCharsNoNewline();
-            fputc('\n', stderr);
+            str->dumpCharsNoNewline(out);
+            out.putChar('\n');
         } else {
             cx->recoverFromOutOfMemory();
         }
     }
 #endif
     args.rval().setUndefined();
     return true;
 }
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1769,89 +1769,89 @@ KidsPointer::checkConsistency(Shape* aKi
         MOZ_ASSERT(isHash());
         KidsHash* hash = toHash();
         KidsHash::Ptr ptr = hash->lookup(StackShape(aKid));
         MOZ_ASSERT(*ptr == aKid);
     }
 }
 
 void
-Shape::dump(FILE* fp) const
+Shape::dump(js::GenericPrinter& out) const
 {
     jsid propid = this->propid();
 
     MOZ_ASSERT(!JSID_IS_VOID(propid));
 
     if (JSID_IS_INT(propid)) {
-        fprintf(fp, "[%ld]", (long) JSID_TO_INT(propid));
+        out.printf("[%ld]", (long) JSID_TO_INT(propid));
     } else if (JSID_IS_ATOM(propid)) {
         if (JSLinearString* str = JSID_TO_ATOM(propid))
-            FileEscapedString(fp, str, '"');
+            EscapedStringPrinter(out, str, '"');
         else
-            fputs("<error>", fp);
+            out.put("<error>");
     } else {
         MOZ_ASSERT(JSID_IS_SYMBOL(propid));
-        JSID_TO_SYMBOL(propid)->dump(fp);
+        JSID_TO_SYMBOL(propid)->dump(out);
     }
 
-    fprintf(fp, " g/s %p/%p slot %d attrs %x ",
-            JS_FUNC_TO_DATA_PTR(void*, getter()),
-            JS_FUNC_TO_DATA_PTR(void*, setter()),
-            hasSlot() ? slot() : -1, attrs);
+    out.printf(" g/s %p/%p slot %d attrs %x ",
+               JS_FUNC_TO_DATA_PTR(void*, getter()),
+               JS_FUNC_TO_DATA_PTR(void*, setter()),
+               hasSlot() ? slot() : -1, attrs);
 
     if (attrs) {
         int first = 1;
-        fputs("(", fp);
-#define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(&(" " #display)[first], fp), first = 0
+        out.putChar('(');
+#define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) out.put(&(" " #display)[first]), first = 0
         DUMP_ATTR(ENUMERATE, enumerate);
         DUMP_ATTR(READONLY, readonly);
         DUMP_ATTR(PERMANENT, permanent);
         DUMP_ATTR(GETTER, getter);
         DUMP_ATTR(SETTER, setter);
         DUMP_ATTR(SHARED, shared);
 #undef  DUMP_ATTR
-        fputs(") ", fp);
+        out.putChar(')');
     }
 
-    fprintf(fp, "flags %x ", flags);
+    out.printf("flags %x ", flags);
     if (flags) {
         int first = 1;
-        fputs("(", fp);
-#define DUMP_FLAG(name, display) if (flags & name) fputs(&(" " #display)[first], fp), first = 0
+        out.putChar('(');
+#define DUMP_FLAG(name, display) if (flags & name) out.put(&(" " #display)[first]), first = 0
         DUMP_FLAG(IN_DICTIONARY, in_dictionary);
 #undef  DUMP_FLAG
-        fputs(") ", fp);
+        out.putChar(')');
     }
 }
 
 void
-Shape::dumpSubtree(int level, FILE* fp) const
+Shape::dumpSubtree(int level, js::GenericPrinter& out) const
 {
     if (!parent) {
         MOZ_ASSERT(level == 0);
         MOZ_ASSERT(JSID_IS_EMPTY(propid_));
-        fprintf(fp, "class %s emptyShape\n", getObjectClass()->name);
+        out.printf("class %s emptyShape\n", getObjectClass()->name);
     } else {
-        fprintf(fp, "%*sid ", level, "");
-        dump(fp);
+        out.printf("%*sid ", level, "");
+        dump(out);
     }
 
     if (!kids.isNull()) {
         ++level;
         if (kids.isShape()) {
             Shape* kid = kids.toShape();
             MOZ_ASSERT(kid->parent == this);
-            kid->dumpSubtree(level, fp);
+            kid->dumpSubtree(level, out);
         } else {
             const KidsHash& hash = *kids.toHash();
             for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
                 Shape* kid = range.front();
 
                 MOZ_ASSERT(kid->parent == this);
-                kid->dumpSubtree(level, fp);
+                kid->dumpSubtree(level, out);
             }
         }
     }
 }
 
 #endif
 
 static bool
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -25,16 +25,17 @@
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "gc/Rooting.h"
 #include "js/HashTable.h"
 #include "js/MemoryMetrics.h"
 #include "js/RootingAPI.h"
 #include "js/UbiNode.h"
 #include "vm/ObjectGroup.h"
+#include "vm/Printer.h"
 #include "vm/String.h"
 #include "vm/Symbol.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4800)
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
@@ -1135,18 +1136,18 @@ class Shape : public gc::TenuredCell
         bool res = isBigEnoughForAShapeTableSlow();
         if (res)
             flags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
         flags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
         return res;
     }
 
 #ifdef DEBUG
-    void dump(FILE* fp) const;
-    void dumpSubtree(int level, FILE* fp) const;
+    void dump(js::GenericPrinter& out) const;
+    void dumpSubtree(int level, js::GenericPrinter& out) const;
 #endif
 
     void sweep();
     void finalize(FreeOp* fop);
     void removeChild(Shape* child);
 
     static const JS::TraceKind TraceKind = JS::TraceKind::Shape;
 
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -100,136 +100,132 @@ JS::ubi::Concrete<JSString>::size(mozill
 }
 
 const char16_t JS::ubi::Concrete<JSString>::concreteTypeName[] = u"JSString";
 
 #ifdef DEBUG
 
 template <typename CharT>
 /*static */ void
-JSString::dumpChars(const CharT* s, size_t n, FILE* fp)
+JSString::dumpChars(const CharT* s, size_t n, js::GenericPrinter& out)
 {
     if (n == SIZE_MAX) {
         n = 0;
         while (s[n])
             n++;
     }
 
-    fputc('"', fp);
+    out.put("\"");
     for (size_t i = 0; i < n; i++) {
         char16_t c = s[i];
         if (c == '\n')
-            fprintf(fp, "\\n");
+            out.put("\\n");
         else if (c == '\t')
-            fprintf(fp, "\\t");
+            out.put("\\t");
         else if (c >= 32 && c < 127)
-            fputc(s[i], fp);
+            out.putChar((char)s[i]);
         else if (c <= 255)
-            fprintf(fp, "\\x%02x", unsigned(c));
+            out.printf("\\x%02x", unsigned(c));
         else
-            fprintf(fp, "\\u%04x", unsigned(c));
+            out.printf("\\u%04x", unsigned(c));
     }
-    fputc('"', fp);
+    out.putChar('"');
 }
 
 template void
-JSString::dumpChars(const Latin1Char* s, size_t n, FILE* fp);
+JSString::dumpChars(const Latin1Char* s, size_t n, js::GenericPrinter& out);
 
 template void
-JSString::dumpChars(const char16_t* s, size_t n, FILE* fp);
+JSString::dumpChars(const char16_t* s, size_t n, js::GenericPrinter& out);
 
 void
-JSString::dumpCharsNoNewline(FILE* fp)
+JSString::dumpCharsNoNewline(js::GenericPrinter& out)
 {
     if (JSLinearString* linear = ensureLinear(nullptr)) {
         AutoCheckCannotGC nogc;
         if (hasLatin1Chars())
-            dumpChars(linear->latin1Chars(nogc), length(), fp);
+            dumpChars(linear->latin1Chars(nogc), length(), out);
         else
-            dumpChars(linear->twoByteChars(nogc), length(), fp);
+            dumpChars(linear->twoByteChars(nogc), length(), out);
     } else {
-        fprintf(fp, "(oom in JSString::dumpCharsNoNewline)");
+        out.put("(oom in JSString::dumpCharsNoNewline)");
     }
 }
 
 void
-JSString::dump(FILE* fp)
+JSString::dump()
+{
+    js::Fprinter out(stderr);
+    dump(out);
+}
+
+void
+JSString::dump(js::GenericPrinter& out)
 {
     if (JSLinearString* linear = ensureLinear(nullptr)) {
         AutoCheckCannotGC nogc;
         if (hasLatin1Chars()) {
             const Latin1Char* chars = linear->latin1Chars(nogc);
-            fprintf(fp, "JSString* (%p) = Latin1Char * (%p) = ", (void*) this,
+            out.printf("JSString* (%p) = Latin1Char * (%p) = ", (void*) this,
                     (void*) chars);
-            dumpChars(chars, length(), fp);
+            dumpChars(chars, length(), out);
         } else {
             const char16_t* chars = linear->twoByteChars(nogc);
-            fprintf(fp, "JSString* (%p) = char16_t * (%p) = ", (void*) this,
+            out.printf("JSString* (%p) = char16_t * (%p) = ", (void*) this,
                     (void*) chars);
-            dumpChars(chars, length(), fp);
+            dumpChars(chars, length(), out);
         }
     } else {
-        fprintf(fp, "(oom in JSString::dump)");
+        out.put("(oom in JSString::dump)");
     }
-    fputc('\n', fp);
+    out.putChar('\n');
 }
 
+
 void
-JSString::dumpCharsNoNewline()
-{
-    dumpCharsNoNewline(stderr);
-}
-
-void
-JSString::dump()
+JSString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dump(stderr);
-}
-
-void
-JSString::dumpRepresentation(FILE* fp, int indent) const
-{
-    if      (isRope())          asRope()        .dumpRepresentation(fp, indent);
-    else if (isDependent())     asDependent()   .dumpRepresentation(fp, indent);
-    else if (isExternal())      asExternal()    .dumpRepresentation(fp, indent);
-    else if (isExtensible())    asExtensible()  .dumpRepresentation(fp, indent);
-    else if (isInline())        asInline()      .dumpRepresentation(fp, indent);
-    else if (isFlat())          asFlat()        .dumpRepresentation(fp, indent);
+    if      (isRope())          asRope()        .dumpRepresentation(out, indent);
+    else if (isDependent())     asDependent()   .dumpRepresentation(out, indent);
+    else if (isExternal())      asExternal()    .dumpRepresentation(out, indent);
+    else if (isExtensible())    asExtensible()  .dumpRepresentation(out, indent);
+    else if (isInline())        asInline()      .dumpRepresentation(out, indent);
+    else if (isFlat())          asFlat()        .dumpRepresentation(out, indent);
     else
         MOZ_CRASH("Unexpected JSString representation");
 }
 
 void
-JSString::dumpRepresentationHeader(FILE* fp, int indent, const char* subclass) const
+JSString::dumpRepresentationHeader(js::GenericPrinter& out, int indent, const char* subclass) const
 {
     uint32_t flags = d.u1.flags;
     // Print the string's address as an actual C++ expression, to facilitate
     // copy-and-paste into a debugger.
-    fprintf(fp, "((%s*) %p) length: %zu  flags: 0x%x", subclass, this, length(), flags);
-    if (flags & FLAT_BIT)               fputs(" FLAT", fp);
-    if (flags & HAS_BASE_BIT)           fputs(" HAS_BASE", fp);
-    if (flags & INLINE_CHARS_BIT)       fputs(" INLINE_CHARS", fp);
-    if (flags & ATOM_BIT)               fputs(" ATOM", fp);
-    if (isPermanentAtom())              fputs(" PERMANENT", fp);
-    if (flags & LATIN1_CHARS_BIT)       fputs(" LATIN1", fp);
-    if (flags & INDEX_VALUE_BIT)        fprintf(fp, " INDEX_VALUE(%u)", getIndexValue());
-    fputc('\n', fp);
+    out.printf("((%s*) %p) length: %zu  flags: 0x%x", subclass, this, length(), flags);
+    if (flags & FLAT_BIT)               out.put(" FLAT");
+    if (flags & HAS_BASE_BIT)           out.put(" HAS_BASE");
+    if (flags & INLINE_CHARS_BIT)       out.put(" INLINE_CHARS");
+    if (flags & ATOM_BIT)               out.put(" ATOM");
+    if (isPermanentAtom())              out.put(" PERMANENT");
+    if (flags & LATIN1_CHARS_BIT)       out.put(" LATIN1");
+    if (flags & INDEX_VALUE_BIT)        out.put(" INDEX_VALUE(%u)", getIndexValue());
+    out.putChar('\n');
 }
 
 void
-JSLinearString::dumpRepresentationChars(FILE* fp, int indent) const
+JSLinearString::dumpRepresentationChars(js::GenericPrinter& out, int indent) const
 {
     if (hasLatin1Chars()) {
-        fprintf(fp, "%*schars: ((Latin1Char*) %p) ", indent, "", rawLatin1Chars());
-        dumpChars(rawLatin1Chars(), length());
+        out.printf("%*schars: ((Latin1Char*) %p) ", indent, "", rawLatin1Chars());
+        dumpChars(rawLatin1Chars(), length(), out);
     } else {
-        fprintf(fp, "%*schars: ((char16_t*) %p) ", indent, "", rawTwoByteChars());
-        dumpChars(rawTwoByteChars(), length());
+        out.printf("%*schars: ((char16_t*) %p) ", indent, "", rawTwoByteChars());
+        dumpChars(rawTwoByteChars(), length(), out);
     }
-    fputc('\n', fp);
+    out.putChar('\n');
 }
 
 bool
 JSString::equals(const char* s)
 {
     JSLinearString* linear = ensureLinear(nullptr);
     if (!linear) {
         fprintf(stderr, "OOM in JSString::equals!\n");
@@ -332,26 +328,26 @@ JSRope::copyCharsInternal(JSContext* cx,
     if (nullTerminate)
         out[n] = 0;
 
     return true;
 }
 
 #ifdef DEBUG
 void
-JSRope::dumpRepresentation(FILE* fp, int indent) const
+JSRope::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent, "JSRope");
+    dumpRepresentationHeader(out, indent, "JSRope");
     indent += 2;
 
-    fprintf(fp, "%*sleft:  ", indent, "");
-    leftChild()->dumpRepresentation(fp, indent);
+    out.printf("%*sleft:  ", indent, "");
+    leftChild()->dumpRepresentation(out, indent);
 
-    fprintf(fp, "%*sright: ", indent, "");
-    rightChild()->dumpRepresentation(fp, indent);
+    out.printf("%*sright: ", indent, "");
+    rightChild()->dumpRepresentation(out, indent);
 }
 #endif
 
 namespace js {
 
 template <>
 void
 CopyChars(char16_t* dest, const JSLinearString& str)
@@ -710,26 +706,26 @@ JSDependentString::undepend(JSContext* c
     MOZ_ASSERT(JSString::isDependent());
     return hasLatin1Chars()
            ? undependInternal<Latin1Char>(cx)
            : undependInternal<char16_t>(cx);
 }
 
 #ifdef DEBUG
 void
-JSDependentString::dumpRepresentation(FILE* fp, int indent) const
+JSDependentString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent, "JSDependentString");
+    dumpRepresentationHeader(out, indent, "JSDependentString");
     indent += 2;
 
     if (mozilla::Maybe<size_t> offset = baseOffset())
-        fprintf(fp, "%*soffset: %zu\n", indent, "", *offset);
+        out.printf("%*soffset: %zu\n", indent, "", *offset);
 
-    fprintf(fp, "%*sbase: ", indent, "");
-    base()->dumpRepresentation(fp, indent);
+    out.printf("%*sbase: ", indent, "");
+    base()->dumpRepresentation(out, indent);
 }
 #endif
 
 template <typename CharT>
 /* static */ bool
 JSFlatString::isIndexSlow(const CharT* s, size_t length, uint32_t* indexp)
 {
     CharT ch = *s;
@@ -1103,36 +1099,37 @@ JSExternalString::ensureFlat(JSContext* 
     setNonInlineChars<char16_t>(s);
     d.u1.flags = FLAT_BIT;
 
     return &this->asFlat();
 }
 
 #ifdef DEBUG
 void
-JSAtom::dump(FILE* fp)
+JSAtom::dump(js::GenericPrinter& out)
 {
-    fprintf(fp, "JSAtom* (%p) = ", (void*) this);
-    this->JSString::dump(fp);
+    out.printf("JSAtom* (%p) = ", (void*) this);
+    this->JSString::dump(out);
 }
 
 void
 JSAtom::dump()
 {
-    dump(stderr);
+    Fprinter out(stderr);
+    dump(out);
 }
 
 void
-JSExternalString::dumpRepresentation(FILE* fp, int indent) const
+JSExternalString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent, "JSExternalString");
+    dumpRepresentationHeader(out, indent, "JSExternalString");
     indent += 2;
 
-    fprintf(fp, "%*sfinalizer: ((JSStringFinalizer*) %p)\n", indent, "", externalFinalizer());
-    dumpRepresentationChars(fp, indent);
+    out.printf("%*sfinalizer: ((JSStringFinalizer*) %p)\n", indent, "", externalFinalizer());
+    dumpRepresentationChars(out, indent);
 }
 #endif /* DEBUG */
 
 JSLinearString*
 js::NewDependentString(JSContext* cx, JSString* baseArg, size_t start, size_t length)
 {
     if (length == 0)
         return cx->emptyString();
@@ -1484,42 +1481,42 @@ NewMaybeExternalString(JSContext* cx, co
     cache.put(str);
     return str;
 }
 
 } /* namespace js */
 
 #ifdef DEBUG
 void
-JSExtensibleString::dumpRepresentation(FILE* fp, int indent) const
+JSExtensibleString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent, "JSExtensibleString");
+    dumpRepresentationHeader(out, indent, "JSExtensibleString");
     indent += 2;
 
-    fprintf(fp, "%*scapacity: %zu\n", indent, "", capacity());
-    dumpRepresentationChars(fp, indent);
+    out.printf("%*scapacity: %zu\n", indent, "", capacity());
+    dumpRepresentationChars(out, indent);
 }
 
 void
-JSInlineString::dumpRepresentation(FILE* fp, int indent) const
+JSInlineString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent,
+    dumpRepresentationHeader(out, indent,
                              isFatInline() ? "JSFatInlineString" : "JSThinInlineString");
     indent += 2;
 
-    dumpRepresentationChars(fp, indent);
+    dumpRepresentationChars(out, indent);
 }
 
 void
-JSFlatString::dumpRepresentation(FILE* fp, int indent) const
+JSFlatString::dumpRepresentation(js::GenericPrinter& out, int indent) const
 {
-    dumpRepresentationHeader(fp, indent, "JSFlatString");
+    dumpRepresentationHeader(out, indent, "JSFlatString");
     indent += 2;
 
-    dumpRepresentationChars(fp, indent);
+    dumpRepresentationChars(out, indent);
 }
 #endif
 
 static void
 FinalizeRepresentativeExternalString(const JSStringFinalizer* fin, char16_t* chars);
 
 static const JSStringFinalizer RepresentativeExternalStringFinalizer =
     { FinalizeRepresentativeExternalString };
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -18,16 +18,18 @@
 #include "gc/Barrier.h"
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "gc/Rooting.h"
 #include "js/CharacterEncoding.h"
 #include "js/GCAPI.h"
 #include "js/RootingAPI.h"
 
+#include "vm/Printer.h"
+
 class JSDependentString;
 class JSExtensibleString;
 class JSExternalString;
 class JSInlineString;
 class JSRope;
 
 namespace js {
 
@@ -521,25 +523,24 @@ class JSString : public js::gc::TenuredC
                       offsetof(JSString, d.s.u2.nonInlineCharsLatin1),
                       "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset");
         return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte);
     }
 
     static const JS::TraceKind TraceKind = JS::TraceKind::String;
 
 #ifdef DEBUG
-    void dump(FILE* fp);
-    void dumpCharsNoNewline(FILE* fp);
-    void dump();
-    void dumpCharsNoNewline();
-    void dumpRepresentation(FILE* fp, int indent) const;
-    void dumpRepresentationHeader(FILE* fp, int indent, const char* subclass) const;
+    void dump(); // Debugger-friendly stderr dump.
+    void dump(js::GenericPrinter& out);
+    void dumpCharsNoNewline(js::GenericPrinter& out);
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
+    void dumpRepresentationHeader(js::GenericPrinter& out, int indent, const char* subclass) const;
 
     template <typename CharT>
-    static void dumpChars(const CharT* s, size_t len, FILE* fp=stderr);
+    static void dumpChars(const CharT* s, size_t len, js::GenericPrinter& out);
 
     bool equals(const char* s);
 #endif
 
     void traceChildren(JSTracer* trc);
 
     static MOZ_ALWAYS_INLINE void readBarrier(JSString* thing) {
         if (thing->isPermanentAtom())
@@ -613,17 +614,17 @@ class JSRope : public JSString
     static size_t offsetOfLeft() {
         return offsetof(JSRope, d.s.u2.left);
     }
     static size_t offsetOfRight() {
         return offsetof(JSRope, d.s.u3.right);
     }
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSRope) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSLinearString : public JSString
 {
@@ -696,17 +697,17 @@ class JSLinearString : public JSString
     char16_t latin1OrTwoByteChar(size_t index) const {
         MOZ_ASSERT(JSString::isLinear());
         MOZ_ASSERT(index < length());
         JS::AutoCheckCannotGC nogc;
         return hasLatin1Chars() ? latin1Chars(nogc)[index] : twoByteChars(nogc)[index];
     }
 
 #ifdef DEBUG
-    void dumpRepresentationChars(FILE* fp, int indent) const;
+    void dumpRepresentationChars(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSLinearString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSDependentString : public JSLinearString
 {
@@ -742,17 +743,17 @@ class JSDependentString : public JSLinea
     static inline JSLinearString* new_(JSContext* cx, JSLinearString* base,
                                        size_t start, size_t length);
 
     inline static size_t offsetOfBase() {
         return offsetof(JSDependentString, d.s.u3.base);
     }
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSDependentString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSFlatString : public JSLinearString
 {
@@ -827,17 +828,17 @@ class JSFlatString : public JSLinearStri
      * operation changes the string to the JSAtom type, in place.
      */
     MOZ_ALWAYS_INLINE JSAtom* morphAtomizedStringIntoAtom(js::HashNumber hash);
     MOZ_ALWAYS_INLINE JSAtom* morphAtomizedStringIntoPermanentAtom(js::HashNumber hash);
 
     inline void finalize(js::FreeOp* fop);
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSFlatString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSExtensibleString : public JSFlatString
 {
@@ -848,17 +849,17 @@ class JSExtensibleString : public JSFlat
   public:
     MOZ_ALWAYS_INLINE
     size_t capacity() const {
         MOZ_ASSERT(JSString::isExtensible());
         return d.s.u3.capacity;
     }
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSExtensibleString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSInlineString : public JSFlatString
 {
@@ -880,17 +881,17 @@ class JSInlineString : public JSFlatStri
     template<typename CharT>
     static bool lengthFits(size_t length);
 
     static size_t offsetOfInlineStorage() {
         return offsetof(JSInlineString, d.inlineStorageTwoByte);
     }
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSInlineString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 /*
  * On 32-bit platforms, JSThinInlineString can store 7 Latin1 characters or 3
@@ -994,17 +995,17 @@ class JSExternalString : public JSLinear
 
     /* Only called by the GC for strings with the AllocKind::EXTERNAL_STRING kind. */
 
     inline void finalize(js::FreeOp* fop);
 
     JSFlatString* ensureFlat(JSContext* cx);
 
 #ifdef DEBUG
-    void dumpRepresentation(FILE* fp, int indent) const;
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
 #endif
 };
 
 static_assert(sizeof(JSExternalString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSUndependedString : public JSFlatString
 {
@@ -1040,17 +1041,17 @@ class JSAtom : public JSFlatString
     MOZ_ALWAYS_INLINE void morphIntoPermanentAtom() {
         d.u1.flags |= PERMANENT_ATOM_MASK;
     }
 
     inline js::HashNumber hash() const;
     inline void initHash(js::HashNumber hash);
 
 #ifdef DEBUG
-    void dump(FILE* fp);
+    void dump(js::GenericPrinter& out);
     void dump();
 #endif
 };
 
 static_assert(sizeof(JSAtom) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 namespace js {
--- a/js/src/vm/Symbol.cpp
+++ b/js/src/vm/Symbol.cpp
@@ -92,35 +92,42 @@ Symbol::for_(JSContext* cx, HandleString
         }
     }
     cx->markAtom(sym);
     return sym;
 }
 
 #ifdef DEBUG
 void
-Symbol::dump(FILE* fp)
+Symbol::dump()
+{
+    js::Fprinter out(stderr);
+    dump(out);
+}
+
+void
+Symbol::dump(js::GenericPrinter& out)
 {
     if (isWellKnownSymbol()) {
         // All the well-known symbol names are ASCII.
-        description_->dumpCharsNoNewline(fp);
+        description_->dumpCharsNoNewline(out);
     } else if (code_ == SymbolCode::InSymbolRegistry || code_ == SymbolCode::UniqueSymbol) {
-        fputs(code_ == SymbolCode::InSymbolRegistry ? "Symbol.for(" : "Symbol(", fp);
+        out.printf(code_ == SymbolCode::InSymbolRegistry ? "Symbol.for(" : "Symbol(");
 
         if (description_)
-            description_->dumpCharsNoNewline(fp);
+            description_->dumpCharsNoNewline(out);
         else
-            fputs("undefined", fp);
+            out.printf("undefined");
 
-        fputc(')', fp);
+        out.putChar(')');
 
         if (code_ == SymbolCode::UniqueSymbol)
-            fprintf(fp, "@%p", (void*) this);
+            out.printf("@%p", (void*) this);
     } else {
-        fprintf(fp, "<Invalid Symbol code=%u>", unsigned(code_));
+        out.printf("<Invalid Symbol code=%u>", unsigned(code_));
     }
 }
 #endif  // DEBUG
 
 bool
 js::SymbolDescriptiveString(JSContext* cx, Symbol* sym, MutableHandleValue result)
 {
     // steps 2-5
--- a/js/src/vm/Symbol.h
+++ b/js/src/vm/Symbol.h
@@ -15,16 +15,17 @@
 #include "jsapi.h"
 
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "js/GCHashTable.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 #include "js/Utility.h"
+#include "vm/Printer.h"
 #include "vm/String.h"
 
 namespace js {
 class AutoLockForExclusiveAccess;
 } // namespace js
 
 namespace JS {
 
@@ -89,17 +90,18 @@ class Symbol : public js::gc::TenuredCel
             thing->asTenured().writeBarrierPre(thing);
     }
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
         return mallocSizeOf(this);
     }
 
 #ifdef DEBUG
-    void dump(FILE* fp = stderr);
+    void dump(); // Debugger-friendly stderr dump.
+    void dump(js::GenericPrinter& out);
 #endif
 };
 
 } /* namespace JS */
 
 namespace js {
 
 /* Hash policy used by the SymbolRegistry. */
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -26,16 +26,17 @@
 #include "jit/CompileInfo.h"
 #include "jit/Ion.h"
 #include "jit/IonAnalysis.h"
 #include "jit/JitCompartment.h"
 #include "jit/OptimizationTracking.h"
 #include "js/MemoryMetrics.h"
 #include "vm/HelperThreads.h"
 #include "vm/Opcodes.h"
+#include "vm/Printer.h"
 #include "vm/Shape.h"
 #include "vm/Time.h"
 #include "vm/UnboxedObject.h"
 
 #include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/NativeObject-inl.h"
@@ -4629,29 +4630,30 @@ void
 TypeScript::printTypes(JSContext* cx, HandleScript script) const
 {
     MOZ_ASSERT(script->types() == this);
 
     if (!script->hasBaselineScript())
         return;
 
     AutoEnterAnalysis enter(nullptr, script->zone());
+    Fprinter out(stderr);
 
     if (script->functionNonDelazifying())
         fprintf(stderr, "Function");
     else if (script->isForEval())
         fprintf(stderr, "Eval");
     else
         fprintf(stderr, "Main");
     fprintf(stderr, " %#" PRIxPTR " %s:%zu ",
             uintptr_t(script.get()), script->filename(), script->lineno());
 
     if (script->functionNonDelazifying()) {
         if (JSAtom* name = script->functionNonDelazifying()->explicitName())
-            name->dumpCharsNoNewline();
+            name->dumpCharsNoNewline(out);
     }
 
     fprintf(stderr, "\n    this:");
     TypeScript::ThisTypes(script)->print();
 
     for (unsigned i = 0;
          script->functionNonDelazifying() && i < script->functionNonDelazifying()->nargs();
          i++)