summaryrefslogtreecommitdiffstats
path: root/js/public/Printer.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/public/Printer.h')
-rw-r--r--js/public/Printer.h565
1 files changed, 565 insertions, 0 deletions
diff --git a/js/public/Printer.h b/js/public/Printer.h
new file mode 100644
index 0000000000..644ffa9176
--- /dev/null
+++ b/js/public/Printer.h
@@ -0,0 +1,565 @@
+/* -*- 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/. */
+
+#ifndef js_Printer_h
+#define js_Printer_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/Range.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "js/TypeDecls.h"
+#include "js/Utility.h"
+
+// [SMDOC] *Printer, Sprinter, Fprinter, ...
+//
+// # Motivation
+//
+// In many places, we want to have functions which are capable of logging
+// various data structures. Previously, we had logging functions for each
+// storage, such as using `fwrite`, `printf` or `snprintf`. In additional cases,
+// many of these logging options were using a string serializing logging
+// function, only to discard the allocated string after it had been copied to a
+// file.
+//
+// GenericPrinter is an answer to avoid excessive amount of temporary
+// allocations which are used once, and a way to make logging functions work
+// independently of the backend they are used with.
+//
+// # Design
+//
+// The GenericPrinter implements most of `put`, `printf`, `vprintf` and
+// `putChar` functions, which are implemented using `put` and `putChar`
+// functions in the derivative classes. Thus, one does not have to reimplement
+// `putString` nor `printf` for each printer.
+//
+// // Logging the value N to whatever printer is provided such as
+// // a file or a string.
+// void logN(GenericPrinter& out) {
+// out.printf("[Logging] %d\n", this->n);
+// }
+//
+// The printing functions are infallible, from the logging functions
+// perspective. If an issue happens while printing, this would be recorded by
+// the Printer, and this can be tested using `hadOutOfMemory` function by the
+// owner of the Printer instance.
+//
+// Even in case of failure, printing functions should remain safe to use. Thus
+// calling `put` twice in a row is safe even if no check for `hadOutOfMemory` is
+// performed. This is necessary to simplify the control flow and avoid bubble up
+// failures out of logging functions.
+//
+// Note, being safe to use does not imply correctness. In case of failure the
+// correctness of the printed characters is no longer guarantee. One should use
+// `hadOutOfMemory` function to know if any failure happened which might have
+// caused incorrect content to be saved. In some cases, such as `Sprinter`,
+// where the string buffer can be extracted, the returned value would account
+// for checking `hadOutOfMemory`.
+//
+// # Implementations
+//
+// The GenericPrinter is a base class where the derivative classes are providing
+// different implementations which have their own advantages and disadvantages:
+//
+// - Fprinter: FILE* printer. Write the content directly to a file.
+//
+// - Sprinter: System allocator C-string buffer. Write the content to a buffer
+// which is reallocated as more content is added. The buffer can then be
+// extracted into a C-string or a JSString, respectively using `release` and
+// `releaseJS`.
+//
+// - LSprinter: LifoAlloc C-string rope. Write the content to a list of chunks
+// in a LifoAlloc buffer, no-reallocation occur but one should use
+// `exportInto` to serialize its content to a Sprinter or a Fprinter. This is
+// useful to avoid reallocation copies, while using an existing LifoAlloc.
+//
+// - SEPrinter: Roughly the same as Fprinter for stderr, except it goes through
+// printf_stderr, which makes sure the output goes to a useful place: the
+// Android log or the Windows debug output.
+//
+// - EscapePrinter: Wrapper around other printers, to escape characters when
+// necessary.
+//
+// # Print UTF-16
+//
+// The GenericPrinter only handle `char` inputs, which is good enough for ASCII
+// and Latin1 character sets. However, to handle UTF-16, one should use an
+// EscapePrinter as well as a policy for escaping characters.
+//
+// One might require different escaping policies based on the escape sequences
+// and based on the set of accepted character for the content generated. For
+// example, JSON does not specify \x<XX> escape sequences.
+//
+// Today the following escape policies exists:
+//
+// - StringEscape: Produce C-like escape sequences: \<c>, \x<XX> and \u<XXXX>.
+// - JSONEscape: Produce JSON escape sequences: \<c> and \u<XXXX>.
+//
+// An escape policy is defined by 2 functions:
+//
+// bool isSafeChar(char16_t c):
+// Returns whether a character can be printed without being escaped.
+//
+// void convertInto(GenericPrinter& out, char16_t c):
+// Calls the printer with the escape sequence for the character given as
+// argument.
+//
+// To use an escape policy, the printer should be wrapped using an EscapePrinter
+// as follows:
+//
+// {
+// // The escaped string is surrounded by double-quotes, escape the double
+// // quotes as well.
+// StringEscape esc('"');
+//
+// // Wrap our existing `GenericPrinter& out` using the `EscapePrinter`.
+// EscapePrinter ep(out, esc);
+//
+// // Append a sequence of characters which might contain UTF-16 characters.
+// ep.put(chars);
+// }
+//
+
+namespace js {
+
+class LifoAlloc;
+
+// Generic printf interface, similar to an ostream in the standard library.
+//
+// This class is useful to make generic printers which can work either with a
+// file backend, with a buffer allocated with an JSContext or a link-list
+// of chunks allocated with a LifoAlloc.
+class JS_PUBLIC_API GenericPrinter {
+ protected:
+ bool hadOOM_; // whether reportOutOfMemory() has been called.
+
+ constexpr GenericPrinter() : hadOOM_(false) {}
+
+ public:
+ // Puts |len| characters from |s| at the current position. This function might
+ // silently fail and the error can be tested using `hadOutOfMemory()`. Calling
+ // this function or any other printing functions after a failures is accepted,
+ // but the outcome would still remain incorrect and `hadOutOfMemory()` would
+ // still report any of the previous errors.
+ virtual void put(const char* s, size_t len) = 0;
+ inline void put(const char* s) { put(s, strlen(s)); }
+
+ // Put a mozilla::Span / mozilla::Range of Latin1Char or char16_t characters
+ // in the output.
+ //
+ // Note that the char16_t variant is expected to crash unless putChar is
+ // overriden to handle properly the full set of WTF-16 character set.
+ virtual void put(mozilla::Span<const JS::Latin1Char> str);
+ virtual void put(mozilla::Span<const char16_t> str);
+
+ // Same as the various put function but only appending a single character.
+ //
+ // Note that the char16_t variant is expected to crash unless putChar is
+ // overriden to handle properly the full set of WTF-16 character set.
+ virtual inline void putChar(const char c) { put(&c, 1); }
+ virtual inline void putChar(const JS::Latin1Char c) { putChar(char(c)); }
+ virtual inline void putChar(const char16_t c) {
+ MOZ_CRASH("Use an EscapePrinter to handle all characters");
+ }
+
+ virtual void putString(JSContext* cx, JSString* str);
+
+ // Prints a formatted string into the buffer.
+ void printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
+ void vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0);
+
+ // In some cases, such as handling JSRopes in a less-quadratic worse-case,
+ // it might be useful to copy content which has already been generated.
+ //
+ // If the buffer is back-readable, then this function should return `true`
+ // and `putFromIndex` should be implemented to delegate to a `put` call at
+ // the matching index and the corresponding length. To provide the index
+ // argument of `putFromIndex`, the `index` method should also be implemented
+ // to return the index within the inner buffer used by the printer.
+ virtual bool canPutFromIndex() const { return false; }
+
+ // Append to the current buffer, bytes which have previously been appended
+ // before.
+ virtual void putFromIndex(size_t index, size_t length) {
+ MOZ_CRASH("Calls to putFromIndex should be guarded by canPutFromIndex.");
+ }
+
+ // When the printer has a seekable buffer and `canPutFromIndex` returns
+ // `true`, this function can return the `index` of the next character to be
+ // added to the buffer.
+ //
+ // This function is monotonic. Thus, if the printer encounter an
+ // Out-Of-Memory issue, then the returned index should be the maximal value
+ // ever returned.
+ virtual size_t index() const { return 0; }
+
+ // In some printers, this ensure that the content is fully written.
+ virtual void flush() { /* Do nothing */
+ }
+
+ // Report that a string operation failed to get the memory it requested.
+ virtual void reportOutOfMemory();
+
+ // Return true if this Sprinter ran out of memory.
+ virtual bool hadOutOfMemory() const { return hadOOM_; }
+};
+
+// Sprintf / JSSprintf, but with unlimited and automatically allocated
+// buffering.
+class JS_PUBLIC_API StringPrinter : public GenericPrinter {
+ public:
+ // Check that the invariant holds at the entry and exit of a scope.
+ struct InvariantChecker {
+ const StringPrinter* parent;
+
+ explicit InvariantChecker(const StringPrinter* p) : parent(p) {
+ parent->checkInvariants();
+ }
+
+ ~InvariantChecker() { parent->checkInvariants(); }
+ };
+
+ JSContext* maybeCx;
+
+ private:
+ static const size_t DefaultSize;
+#ifdef DEBUG
+ bool initialized; // true if this is initialized, use for debug builds
+#endif
+ bool shouldReportOOM; // whether to report OOM to the maybeCx
+ char* base; // malloc'd buffer address
+ size_t size; // size of buffer allocated at base
+ ptrdiff_t offset; // offset of next free char in buffer
+
+ // The arena to be used by jemalloc to allocate the string into. This is
+ // selected by the child classes when calling the constructor. JSStrings have
+ // a different arena than strings which do not belong to the JS engine, and as
+ // such when building a JSString with the intent of avoiding reallocation, the
+ // destination arena has to be selected upfront.
+ arena_id_t arena;
+
+ private:
+ [[nodiscard]] bool realloc_(size_t newSize);
+
+ protected:
+ // JSContext* parameter is optional and can be omitted if the following
+ // are not used.
+ // * putString method with JSString
+ // * QuoteString function with JSString
+ // * JSONQuoteString function with JSString
+ //
+ // If JSContext* parameter is not provided, or shouldReportOOM is false,
+ // the consumer should manually report OOM on any failure.
+ explicit StringPrinter(arena_id_t arena, JSContext* maybeCx = nullptr,
+ bool shouldReportOOM = true);
+ ~StringPrinter();
+
+ JS::UniqueChars releaseChars();
+ JSString* releaseJS(JSContext* cx);
+
+ public:
+ // Initialize this sprinter, returns false on error.
+ [[nodiscard]] bool init();
+
+ void checkInvariants() const;
+
+ // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the
+ // attempt succeeds, return a pointer to the start of that space and adjust
+ // the internal content. The caller *must* completely fill this space on
+ // success.
+ char* reserve(size_t len);
+
+ // Puts |len| characters from |s| at the current position. May OOM, which must
+ // be checked by testing the return value of releaseJS() at the end of
+ // printing.
+ virtual void put(const char* s, size_t len) final;
+ using GenericPrinter::put; // pick up |put(const char* s);|
+
+ virtual bool canPutFromIndex() const final { return true; }
+ virtual void putFromIndex(size_t index, size_t length) final {
+ MOZ_ASSERT(index <= this->index());
+ MOZ_ASSERT(index + length <= this->index());
+ put(base + index, length);
+ }
+ virtual size_t index() const final { return length(); }
+
+ virtual void putString(JSContext* cx, JSString* str) final;
+
+ size_t length() const;
+
+ // When an OOM has already been reported on the Sprinter, this function will
+ // forward this error to the JSContext given in the Sprinter initialization.
+ //
+ // If no JSContext had been provided or the Sprinter is configured to not
+ // report OOM, then nothing happens.
+ void forwardOutOfMemory();
+};
+
+class JS_PUBLIC_API Sprinter : public StringPrinter {
+ public:
+ explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true)
+ : StringPrinter(js::MallocArena, maybeCx, shouldReportOOM) {}
+ ~Sprinter() {}
+
+ JS::UniqueChars release() { return releaseChars(); }
+};
+
+class JS_PUBLIC_API JSSprinter : public StringPrinter {
+ public:
+ explicit JSSprinter(JSContext* cx)
+ : StringPrinter(js::StringBufferArena, cx, true) {}
+ ~JSSprinter() {}
+
+ JSString* release(JSContext* cx) { return releaseJS(cx); }
+};
+
+// Fprinter, print a string directly into a file.
+class JS_PUBLIC_API Fprinter final : public GenericPrinter {
+ private:
+ FILE* file_;
+ bool init_;
+
+ public:
+ explicit Fprinter(FILE* fp);
+
+ constexpr Fprinter() : file_(nullptr), init_(false) {}
+
+#ifdef DEBUG
+ ~Fprinter();
+#endif
+
+ // Initialize this printer, returns false on error.
+ [[nodiscard]] bool init(const char* path);
+ void init(FILE* fp);
+ bool isInitialized() const { return file_ != nullptr; }
+ void flush() override;
+ void finish();
+
+ // Puts |len| characters from |s| at the current position. Errors may be
+ // detected with hadOutOfMemory() (which will be set for any fwrite() error,
+ // not just OOM.)
+ void put(const char* s, size_t len) override;
+ using GenericPrinter::put; // pick up |put(const char* s);|
+};
+
+// SEprinter, print using printf_stderr (goes to Android log, Windows debug,
+// else just stderr).
+class SEprinter final : public GenericPrinter {
+ public:
+ constexpr SEprinter() {}
+
+ // Puts |len| characters from |s| at the current position. Ignores errors.
+ virtual void put(const char* s, size_t len) override {
+ printf_stderr("%.*s", int(len), s);
+ }
+ using GenericPrinter::put; // pick up |put(const char* s);|
+};
+
+// LSprinter, is similar to Sprinter except that instead of using an
+// JSContext to allocate strings, it use a LifoAlloc as a backend for the
+// allocation of the chunk of the string.
+class JS_PUBLIC_API LSprinter final : public GenericPrinter {
+ private:
+ struct Chunk {
+ Chunk* next;
+ size_t length;
+
+ char* chars() { return reinterpret_cast<char*>(this + 1); }
+ char* end() { return chars() + length; }
+ };
+
+ private:
+ LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations.
+ Chunk* head_;
+ Chunk* tail_;
+ size_t unused_;
+
+ public:
+ explicit LSprinter(LifoAlloc* lifoAlloc);
+ ~LSprinter();
+
+ // Copy the content of the chunks into another printer, such that we can
+ // flush the content of this printer to a file.
+ void exportInto(GenericPrinter& out) const;
+
+ // Drop the current string, and let them be free with the LifoAlloc.
+ void clear();
+
+ // Puts |len| characters from |s| at the current position.
+ virtual void put(const char* s, size_t len) override;
+ using GenericPrinter::put; // pick up |put(const char* s);|
+};
+
+// Escaping printers work like any other printer except that any added character
+// are checked for escaping sequences. This one would escape a string such that
+// it can safely be embedded in a JS string.
+template <typename Delegate, typename Escape>
+class JS_PUBLIC_API EscapePrinter final : public GenericPrinter {
+ size_t lengthOfSafeChars(const char* s, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ if (!esc.isSafeChar(uint8_t(s[i]))) {
+ return i;
+ }
+ }
+ return len;
+ }
+
+ private:
+ Delegate& out;
+ Escape& esc;
+
+ public:
+ EscapePrinter(Delegate& out, Escape& esc) : out(out), esc(esc) {}
+ ~EscapePrinter() {}
+
+ using GenericPrinter::put;
+ void put(const char* s, size_t len) override {
+ const char* b = s;
+ while (len) {
+ size_t index = lengthOfSafeChars(b, len);
+ if (index) {
+ out.put(b, index);
+ len -= index;
+ b += index;
+ }
+ if (len) {
+ esc.convertInto(out, char16_t(uint8_t(*b)));
+ len -= 1;
+ b += 1;
+ }
+ }
+ }
+
+ inline void putChar(const char c) override {
+ if (esc.isSafeChar(char16_t(uint8_t(c)))) {
+ out.putChar(char(c));
+ return;
+ }
+ esc.convertInto(out, char16_t(uint8_t(c)));
+ }
+
+ inline void putChar(const JS::Latin1Char c) override {
+ if (esc.isSafeChar(char16_t(c))) {
+ out.putChar(char(c));
+ return;
+ }
+ esc.convertInto(out, char16_t(c));
+ }
+
+ inline void putChar(const char16_t c) override {
+ if (esc.isSafeChar(c)) {
+ out.putChar(char(c));
+ return;
+ }
+ esc.convertInto(out, c);
+ }
+
+ // Forward calls to delegated printer.
+ bool canPutFromIndex() const override { return out.canPutFromIndex(); }
+ void putFromIndex(size_t index, size_t length) final {
+ out.putFromIndex(index, length);
+ }
+ size_t index() const final { return out.index(); }
+ void flush() final { out.flush(); }
+ void reportOutOfMemory() final { out.reportOutOfMemory(); }
+ bool hadOutOfMemory() const final { return out.hadOutOfMemory(); }
+};
+
+class JS_PUBLIC_API JSONEscape {
+ public:
+ bool isSafeChar(char16_t c);
+ void convertInto(GenericPrinter& out, char16_t c);
+};
+
+class JS_PUBLIC_API StringEscape {
+ private:
+ const char quote = '\0';
+
+ public:
+ explicit StringEscape(const char quote = '\0') : quote(quote) {}
+
+ bool isSafeChar(char16_t c);
+ void convertInto(GenericPrinter& out, char16_t c);
+};
+
+// A GenericPrinter that formats everything at a nested indentation level.
+class JS_PUBLIC_API IndentedPrinter final : public GenericPrinter {
+ GenericPrinter& out_;
+ // The number of indents to insert at the beginning of each line.
+ uint32_t indentLevel_;
+ // The number of spaces to insert for each indent.
+ uint32_t indentAmount_;
+ // Whether we have seen a line ending and should insert an indent at the
+ // next line fragment.
+ bool pendingIndent_;
+
+ // Put an indent to `out_`
+ void putIndent();
+ // Put `s` to `out_`, inserting an indent if we need to
+ void putWithMaybeIndent(const char* s, size_t len);
+
+ public:
+ explicit IndentedPrinter(GenericPrinter& out, uint32_t indentLevel = 0,
+ uint32_t indentAmount = 2)
+ : out_(out),
+ indentLevel_(indentLevel),
+ indentAmount_(indentAmount),
+ pendingIndent_(false) {}
+
+ // Automatically insert and remove and indent for a scope
+ class AutoIndent {
+ IndentedPrinter& printer_;
+
+ public:
+ explicit AutoIndent(IndentedPrinter& printer) : printer_(printer) {
+ printer_.setIndentLevel(printer_.indentLevel() + 1);
+ }
+ ~AutoIndent() { printer_.setIndentLevel(printer_.indentLevel() - 1); }
+ };
+
+ uint32_t indentLevel() const { return indentLevel_; }
+ void setIndentLevel(uint32_t indentLevel) { indentLevel_ = indentLevel; }
+
+ virtual void put(const char* s, size_t len) override;
+ using GenericPrinter::put; // pick up |inline void put(const char* s);|
+};
+
+// Map escaped code to the letter/symbol escaped with a backslash.
+extern const char js_EscapeMap[];
+
+// Return a C-string containing the chars in str, with any non-printing chars
+// escaped. If the optional quote parameter is present and is not '\0', quotes
+// (as specified by the quote argument) are also escaped, and the quote
+// character is appended at the beginning and end of the result string.
+// The returned string is guaranteed to contain only ASCII characters.
+extern JS_PUBLIC_API JS::UniqueChars QuoteString(JSContext* cx, JSString* str,
+ char quote = '\0');
+
+// Appends the quoted string to the given Sprinter. Follows the same semantics
+// as QuoteString from above.
+extern JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str,
+ char quote = '\0');
+
+// Appends the quoted string to the given Sprinter. Follows the same
+// Appends the JSON quoted string to the given Sprinter.
+extern JS_PUBLIC_API void JSONQuoteString(StringPrinter* sp, JSString* str);
+
+// Internal implementation code for QuoteString methods above.
+enum class QuoteTarget { String, JSON };
+
+template <QuoteTarget target, typename CharT>
+void JS_PUBLIC_API QuoteString(Sprinter* sp,
+ const mozilla::Range<const CharT>& chars,
+ char quote = '\0');
+
+} // namespace js
+
+#endif // js_Printer_h