summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Printer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/Printer.cpp')
-rw-r--r--js/src/vm/Printer.cpp630
1 files changed, 630 insertions, 0 deletions
diff --git a/js/src/vm/Printer.cpp b/js/src/vm/Printer.cpp
new file mode 100644
index 0000000000..614273eee8
--- /dev/null
+++ b/js/src/vm/Printer.cpp
@@ -0,0 +1,630 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/Printer.h"
+
+#include "mozilla/PodOperations.h"
+#include "mozilla/Printf.h"
+#include "mozilla/RangedPtr.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "ds/LifoAlloc.h"
+#include "js/CharacterEncoding.h"
+#include "util/Memory.h"
+#include "util/Text.h"
+#include "util/WindowsWrapper.h"
+#include "vm/StringType.h"
+
+using mozilla::PodCopy;
+
+namespace {
+
+class GenericPrinterPrintfTarget : public mozilla::PrintfTarget {
+ public:
+ explicit GenericPrinterPrintfTarget(js::GenericPrinter& p) : printer(p) {}
+
+ bool append(const char* sp, size_t len) override {
+ printer.put(sp, len);
+ return true;
+ }
+
+ private:
+ js::GenericPrinter& printer;
+};
+
+} // namespace
+
+namespace js {
+
+void GenericPrinter::reportOutOfMemory() {
+ if (hadOOM_) {
+ return;
+ }
+ hadOOM_ = true;
+}
+
+void GenericPrinter::put(mozilla::Span<const JS::Latin1Char> str) {
+ if (!str.Length()) {
+ return;
+ }
+ put(reinterpret_cast<const char*>(&str[0]), str.Length());
+}
+
+void GenericPrinter::put(mozilla::Span<const char16_t> str) {
+ for (char16_t c : str) {
+ putChar(c);
+ }
+}
+
+void GenericPrinter::putString(JSContext* cx, JSString* str) {
+ StringSegmentRange iter(cx);
+ if (!iter.init(str)) {
+ reportOutOfMemory();
+ return;
+ }
+ JS::AutoCheckCannotGC nogc;
+ while (!iter.empty()) {
+ JSLinearString* linear = iter.front();
+ if (linear->hasLatin1Chars()) {
+ put(linear->latin1Range(nogc));
+ } else {
+ put(linear->twoByteRange(nogc));
+ }
+ if (!iter.popFront()) {
+ reportOutOfMemory();
+ return;
+ }
+ }
+}
+
+void GenericPrinter::printf(const char* fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ vprintf(fmt, va);
+ va_end(va);
+}
+
+void GenericPrinter::vprintf(const char* fmt, va_list ap) {
+ // Simple shortcut to avoid allocating strings.
+ if (strchr(fmt, '%') == nullptr) {
+ put(fmt);
+ return;
+ }
+
+ GenericPrinterPrintfTarget printer(*this);
+ (void)printer.vprint(fmt, ap);
+}
+
+const size_t StringPrinter::DefaultSize = 64;
+
+bool StringPrinter::realloc_(size_t newSize) {
+ MOZ_ASSERT(newSize > (size_t)offset);
+ if (hadOOM_) {
+ return false;
+ }
+ char* newBuf = (char*)js_arena_realloc(arena, base, newSize);
+ if (!newBuf) {
+ reportOutOfMemory();
+ return false;
+ }
+ base = newBuf;
+ size = newSize;
+ base[size - 1] = '\0';
+ return true;
+}
+
+StringPrinter::StringPrinter(arena_id_t arena, JSContext* maybeCx,
+ bool shouldReportOOM)
+ : maybeCx(maybeCx),
+#ifdef DEBUG
+ initialized(false),
+#endif
+ shouldReportOOM(maybeCx && shouldReportOOM),
+ base(nullptr),
+ size(0),
+ offset(0),
+ arena(arena) {
+}
+
+StringPrinter::~StringPrinter() {
+#ifdef DEBUG
+ if (initialized) {
+ checkInvariants();
+ }
+#endif
+ js_free(base);
+}
+
+bool StringPrinter::init() {
+ MOZ_ASSERT(!initialized);
+ base = js_pod_arena_malloc<char>(arena, DefaultSize);
+ if (!base) {
+ reportOutOfMemory();
+ forwardOutOfMemory();
+ return false;
+ }
+#ifdef DEBUG
+ initialized = true;
+#endif
+ *base = '\0';
+ size = DefaultSize;
+ base[size - 1] = '\0';
+ return true;
+}
+
+void StringPrinter::checkInvariants() const {
+ MOZ_ASSERT(initialized);
+ MOZ_ASSERT((size_t)offset < size);
+ MOZ_ASSERT(base[size - 1] == '\0');
+}
+
+UniqueChars StringPrinter::releaseChars() {
+ if (hadOOM_) {
+ forwardOutOfMemory();
+ return nullptr;
+ }
+
+ checkInvariants();
+ char* str = base;
+ base = nullptr;
+ offset = size = 0;
+#ifdef DEBUG
+ initialized = false;
+#endif
+ return UniqueChars(str);
+}
+
+JSString* StringPrinter::releaseJS(JSContext* cx) {
+ if (hadOOM_) {
+ MOZ_ASSERT_IF(maybeCx, maybeCx == cx);
+ forwardOutOfMemory();
+ return nullptr;
+ }
+
+ checkInvariants();
+
+ // Extract StringPrinter data.
+ size_t len = length();
+ UniqueChars str(base);
+
+ // Reset StringPrinter.
+ base = nullptr;
+ offset = 0;
+ size = 0;
+#ifdef DEBUG
+ initialized = false;
+#endif
+
+ // Convert extracted data to a JSString, reusing the current buffer whenever
+ // possible.
+ JS::UTF8Chars utf8(str.get(), len);
+ JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
+ if (encoding == JS::SmallestEncoding::ASCII) {
+ UniqueLatin1Chars latin1(reinterpret_cast<JS::Latin1Char*>(str.release()));
+ return js::NewString<js::CanGC>(cx, std::move(latin1), len);
+ }
+
+ size_t length;
+ if (encoding == JS::SmallestEncoding::Latin1) {
+ UniqueLatin1Chars latin1(
+ UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena)
+ .get());
+ if (!latin1) {
+ return nullptr;
+ }
+
+ return js::NewString<js::CanGC>(cx, std::move(latin1), length);
+ }
+
+ MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16);
+
+ UniqueTwoByteChars utf16(
+ UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena)
+ .get());
+ if (!utf16) {
+ return nullptr;
+ }
+
+ return js::NewString<js::CanGC>(cx, std::move(utf16), length);
+}
+
+char* StringPrinter::reserve(size_t len) {
+ InvariantChecker ic(this);
+
+ while (len + 1 > size - offset) { /* Include trailing \0 */
+ if (!realloc_(size * 2)) {
+ return nullptr;
+ }
+ }
+
+ char* sb = base + offset;
+ offset += len;
+ return sb;
+}
+
+void StringPrinter::put(const char* s, size_t len) {
+ InvariantChecker ic(this);
+
+ const char* oldBase = base;
+ const char* oldEnd = base + size;
+
+ char* bp = reserve(len);
+ if (!bp) {
+ return;
+ }
+
+ // s is within the buffer already
+ if (s >= oldBase && s < oldEnd) {
+ // Update the source pointer in case of a realloc-ation.
+ size_t index = s - oldBase;
+ s = &base[index];
+ memmove(bp, s, len);
+ } else {
+ js_memcpy(bp, s, len);
+ }
+
+ bp[len] = '\0';
+}
+
+void StringPrinter::putString(JSContext* cx, JSString* s) {
+ MOZ_ASSERT(cx);
+ InvariantChecker ic(this);
+
+ JSLinearString* linear = s->ensureLinear(cx);
+ if (!linear) {
+ return;
+ }
+
+ size_t length = JS::GetDeflatedUTF8StringLength(linear);
+
+ char* buffer = reserve(length);
+ if (!buffer) {
+ return;
+ }
+
+ mozilla::DebugOnly<size_t> written =
+ JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(buffer, length));
+ MOZ_ASSERT(written == length);
+
+ buffer[length] = '\0';
+}
+
+size_t StringPrinter::length() const { return size_t(offset); }
+
+void StringPrinter::forwardOutOfMemory() {
+ MOZ_ASSERT(hadOOM_);
+ if (maybeCx && shouldReportOOM) {
+ ReportOutOfMemory(maybeCx);
+ }
+}
+
+const char js_EscapeMap[] = {
+ // clang-format off
+ '\b', 'b',
+ '\f', 'f',
+ '\n', 'n',
+ '\r', 'r',
+ '\t', 't',
+ '\v', 'v',
+ '"', '"',
+ '\'', '\'',
+ '\\', '\\',
+ '\0'
+ // clang-format on
+};
+
+static const char JSONEscapeMap[] = {
+ // clang-format off
+ '\b', 'b',
+ '\f', 'f',
+ '\n', 'n',
+ '\r', 'r',
+ '\t', 't',
+ '"', '"',
+ '\\', '\\',
+ '\0'
+ // clang-format on
+};
+
+template <QuoteTarget target, typename CharT>
+JS_PUBLIC_API void QuoteString(Sprinter* sp,
+ const mozilla::Range<const CharT>& chars,
+ char quote) {
+ MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0');
+
+ if (quote) {
+ sp->putChar(quote);
+ }
+ if (target == QuoteTarget::String) {
+ StringEscape esc(quote);
+ EscapePrinter ep(*sp, esc);
+ ep.put(chars);
+ } else {
+ MOZ_ASSERT(target == QuoteTarget::JSON);
+ JSONEscape esc;
+ EscapePrinter ep(*sp, esc);
+ ep.put(chars);
+ }
+ if (quote) {
+ sp->putChar(quote);
+ }
+}
+
+template JS_PUBLIC_API void QuoteString<QuoteTarget::String, Latin1Char>(
+ Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
+
+template JS_PUBLIC_API void QuoteString<QuoteTarget::String, char16_t>(
+ Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
+
+template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, Latin1Char>(
+ Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
+
+template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, char16_t>(
+ Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
+
+JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str,
+ char quote /*= '\0' */) {
+ MOZ_ASSERT(sp->maybeCx);
+ if (quote) {
+ sp->putChar(quote);
+ }
+ StringEscape esc(quote);
+ EscapePrinter ep(*sp, esc);
+ ep.putString(sp->maybeCx, str);
+ if (quote) {
+ sp->putChar(quote);
+ }
+}
+
+JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str,
+ char quote /* = '\0' */) {
+ Sprinter sprinter(cx);
+ if (!sprinter.init()) {
+ return nullptr;
+ }
+ QuoteString(&sprinter, str, quote);
+ return sprinter.release();
+}
+
+JS_PUBLIC_API void JSONQuoteString(StringPrinter* sp, JSString* str) {
+ MOZ_ASSERT(sp->maybeCx);
+ JSONEscape esc;
+ EscapePrinter ep(*sp, esc);
+ ep.putString(sp->maybeCx, str);
+}
+
+Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); }
+
+#ifdef DEBUG
+Fprinter::~Fprinter() { MOZ_ASSERT_IF(init_, !file_); }
+#endif
+
+bool Fprinter::init(const char* path) {
+ MOZ_ASSERT(!file_);
+ file_ = fopen(path, "w");
+ if (!file_) {
+ return false;
+ }
+ init_ = true;
+ return true;
+}
+
+void Fprinter::init(FILE* fp) {
+ MOZ_ASSERT(!file_);
+ file_ = fp;
+ init_ = false;
+}
+
+void Fprinter::flush() {
+ MOZ_ASSERT(file_);
+ fflush(file_);
+}
+
+void Fprinter::finish() {
+ MOZ_ASSERT(file_);
+ if (init_) {
+ fclose(file_);
+ }
+ file_ = nullptr;
+}
+
+void Fprinter::put(const char* s, size_t len) {
+ if (hadOutOfMemory()) {
+ return;
+ }
+
+ MOZ_ASSERT(file_);
+ int i = fwrite(s, /*size=*/1, /*nitems=*/len, file_);
+ if (size_t(i) != len) {
+ reportOutOfMemory();
+ return;
+ }
+#ifdef XP_WIN
+ if ((file_ == stderr) && (IsDebuggerPresent())) {
+ UniqueChars buf = DuplicateString(s, len);
+ if (!buf) {
+ reportOutOfMemory();
+ return;
+ }
+ OutputDebugStringA(buf.get());
+ }
+#endif
+}
+
+LSprinter::LSprinter(LifoAlloc* lifoAlloc)
+ : alloc_(lifoAlloc), head_(nullptr), tail_(nullptr), unused_(0) {}
+
+LSprinter::~LSprinter() {
+ // This LSprinter might be allocated as part of the same LifoAlloc, so we
+ // should not expect the destructor to be called.
+}
+
+void LSprinter::exportInto(GenericPrinter& out) const {
+ if (!head_) {
+ return;
+ }
+
+ for (Chunk* it = head_; it != tail_; it = it->next) {
+ out.put(it->chars(), it->length);
+ }
+ out.put(tail_->chars(), tail_->length - unused_);
+}
+
+void LSprinter::clear() {
+ head_ = nullptr;
+ tail_ = nullptr;
+ unused_ = 0;
+ hadOOM_ = false;
+}
+
+void LSprinter::put(const char* s, size_t len) {
+ if (hadOutOfMemory()) {
+ return;
+ }
+
+ // Compute how much data will fit in the current chunk.
+ size_t existingSpaceWrite = 0;
+ size_t overflow = len;
+ if (unused_ > 0 && tail_) {
+ existingSpaceWrite = std::min(unused_, len);
+ overflow = len - existingSpaceWrite;
+ }
+
+ // If necessary, allocate a new chunk for overflow data.
+ size_t allocLength = 0;
+ Chunk* last = nullptr;
+ if (overflow > 0) {
+ allocLength =
+ AlignBytes(sizeof(Chunk) + overflow, js::detail::LIFO_ALLOC_ALIGN);
+
+ LifoAlloc::AutoFallibleScope fallibleAllocator(alloc_);
+ last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength));
+ if (!last) {
+ reportOutOfMemory();
+ return;
+ }
+ }
+
+ // All fallible operations complete: now fill up existing space, then
+ // overflow space in any new chunk.
+ MOZ_ASSERT(existingSpaceWrite + overflow == len);
+
+ if (existingSpaceWrite > 0) {
+ PodCopy(tail_->end() - unused_, s, existingSpaceWrite);
+ unused_ -= existingSpaceWrite;
+ s += existingSpaceWrite;
+ }
+
+ if (overflow > 0) {
+ if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) {
+ // tail_ and last are consecutive in memory. LifoAlloc has no
+ // metadata and is just a bump allocator, so we can cheat by
+ // appending the newly-allocated space to tail_.
+ unused_ = allocLength;
+ tail_->length += allocLength;
+ } else {
+ // Remove the size of the header from the allocated length.
+ size_t availableSpace = allocLength - sizeof(Chunk);
+ last->next = nullptr;
+ last->length = availableSpace;
+
+ unused_ = availableSpace;
+ if (!head_) {
+ head_ = last;
+ } else {
+ tail_->next = last;
+ }
+
+ tail_ = last;
+ }
+
+ PodCopy(tail_->end() - unused_, s, overflow);
+
+ MOZ_ASSERT(unused_ >= overflow);
+ unused_ -= overflow;
+ }
+
+ MOZ_ASSERT(len <= INT_MAX);
+}
+
+bool JSONEscape::isSafeChar(char16_t c) {
+ return js::IsAsciiPrintable(c) && c != '"' && c != '\\';
+}
+
+void JSONEscape::convertInto(GenericPrinter& out, char16_t c) {
+ const char* escape = nullptr;
+ if (!(c >> 8) && c != 0 &&
+ (escape = strchr(JSONEscapeMap, int(c))) != nullptr) {
+ out.printf("\\%c", escape[1]);
+ } else {
+ out.printf("\\u%04X", c);
+ }
+}
+
+bool StringEscape::isSafeChar(char16_t c) {
+ return js::IsAsciiPrintable(c) && c != quote && c != '\\';
+}
+
+void StringEscape::convertInto(GenericPrinter& out, char16_t c) {
+ const char* escape = nullptr;
+ if (!(c >> 8) && c != 0 &&
+ (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
+ out.printf("\\%c", escape[1]);
+ } else {
+ // Use \x only if the high byte is 0 and we're in a quoted string, because
+ // ECMA-262 allows only \u, not \x, in Unicode identifiers (see bug 621814).
+ out.printf(!(c >> 8) ? "\\x%02X" : "\\u%04X", c);
+ }
+}
+
+void IndentedPrinter::putIndent() {
+ // Allocate a static buffer of 16 spaces (plus null terminator) and use that
+ // in batches for the total number of spaces we need to put.
+ static const char spaceBuffer[17] = " ";
+ size_t remainingSpaces = indentLevel_ * indentAmount_;
+ while (remainingSpaces > 16) {
+ out_.put(spaceBuffer, 16);
+ remainingSpaces -= 16;
+ }
+ if (remainingSpaces) {
+ out_.put(spaceBuffer, remainingSpaces);
+ }
+}
+
+void IndentedPrinter::putWithMaybeIndent(const char* s, size_t len) {
+ if (len == 0) {
+ return;
+ }
+ if (pendingIndent_) {
+ putIndent();
+ pendingIndent_ = false;
+ }
+ out_.put(s, len);
+}
+
+void IndentedPrinter::put(const char* s, size_t len) {
+ const char* current = s;
+ // Split the text into lines and output each with an indent
+ while (const char* nextLineEnd = (const char*)memchr(current, '\n', len)) {
+ // Put this line (including the new-line)
+ size_t lineWithNewLineSize = nextLineEnd - current + 1;
+ putWithMaybeIndent(current, lineWithNewLineSize);
+
+ // The next put should have an indent added
+ pendingIndent_ = true;
+
+ // Advance the cursor
+ current += lineWithNewLineSize;
+ len -= lineWithNewLineSize;
+ }
+
+ // Put any remaining text
+ putWithMaybeIndent(current, len);
+}
+
+} // namespace js