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.cpp618
1 files changed, 618 insertions, 0 deletions
diff --git a/js/src/vm/Printer.cpp b/js/src/vm/Printer.cpp
new file mode 100644
index 000000000..88350a4bd
--- /dev/null
+++ b/js/src/vm/Printer.cpp
@@ -0,0 +1,618 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "vm/Printer.h"
+
+#include "mozilla/PodOperations.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "jscntxt.h"
+#include "jsprf.h"
+#include "jsutil.h"
+
+#include "ds/LifoAlloc.h"
+
+using mozilla::PodCopy;
+
+namespace js {
+
+GenericPrinter::GenericPrinter()
+ : hadOOM_(false)
+{
+}
+
+void
+GenericPrinter::reportOutOfMemory()
+{
+ if (hadOOM_)
+ return;
+ hadOOM_ = true;
+}
+
+bool
+GenericPrinter::hadOutOfMemory() const
+{
+ return hadOOM_;
+}
+
+int
+GenericPrinter::printf(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int i = vprintf(fmt, va);
+ va_end(va);
+ return i;
+}
+
+int
+GenericPrinter::vprintf(const char* fmt, va_list ap)
+{
+ // Simple shortcut to avoid allocating strings.
+ if (strchr(fmt, '%') == nullptr)
+ return put(fmt);
+
+ char* bp;
+ bp = JS_vsmprintf(fmt, ap); /* XXX vsaprintf */
+ if (!bp) {
+ reportOutOfMemory();
+ return -1;
+ }
+ int i = put(bp);
+ js_free(bp);
+ return i;
+}
+
+const size_t Sprinter::DefaultSize = 64;
+
+bool
+Sprinter::realloc_(size_t newSize)
+{
+ MOZ_ASSERT(newSize > (size_t) offset);
+ char* newBuf = (char*) js_realloc(base, newSize);
+ if (!newBuf) {
+ reportOutOfMemory();
+ return false;
+ }
+ base = newBuf;
+ size = newSize;
+ base[size - 1] = 0;
+ return true;
+}
+
+Sprinter::Sprinter(ExclusiveContext* cx, bool shouldReportOOM)
+ : context(cx),
+#ifdef DEBUG
+ initialized(false),
+#endif
+ shouldReportOOM(shouldReportOOM),
+ base(nullptr), size(0), offset(0)
+{ }
+
+Sprinter::~Sprinter()
+{
+#ifdef DEBUG
+ if (initialized)
+ checkInvariants();
+#endif
+ js_free(base);
+}
+
+bool
+Sprinter::init()
+{
+ MOZ_ASSERT(!initialized);
+ base = (char*) js_malloc(DefaultSize);
+ if (!base) {
+ reportOutOfMemory();
+ return false;
+ }
+#ifdef DEBUG
+ initialized = true;
+#endif
+ *base = 0;
+ size = DefaultSize;
+ base[size - 1] = 0;
+ return true;
+}
+
+void
+Sprinter::checkInvariants() const
+{
+ MOZ_ASSERT(initialized);
+ MOZ_ASSERT((size_t) offset < size);
+ MOZ_ASSERT(base[size - 1] == 0);
+}
+
+const char*
+Sprinter::string() const
+{
+ return base;
+}
+
+const char*
+Sprinter::stringEnd() const
+{
+ return base + offset;
+}
+
+char*
+Sprinter::stringAt(ptrdiff_t off) const
+{
+ MOZ_ASSERT(off >= 0 && (size_t) off < size);
+ return base + off;
+}
+
+char&
+Sprinter::operator[](size_t off)
+{
+ MOZ_ASSERT(off < size);
+ return *(base + off);
+}
+
+char*
+Sprinter::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;
+}
+
+int
+Sprinter::put(const char* s, size_t len)
+{
+ InvariantChecker ic(this);
+
+ const char* oldBase = base;
+ const char* oldEnd = base + size;
+
+ ptrdiff_t oldOffset = offset;
+ char* bp = reserve(len);
+ if (!bp)
+ return -1;
+
+ /* s is within the buffer already */
+ if (s >= oldBase && s < oldEnd) {
+ /* buffer was realloc'ed */
+ if (base != oldBase)
+ s = stringAt(s - oldBase); /* this is where it lives now */
+ memmove(bp, s, len);
+ } else {
+ js_memcpy(bp, s, len);
+ }
+
+ bp[len] = 0;
+ return oldOffset;
+}
+
+int
+Sprinter::vprintf(const char* fmt, va_list ap)
+{
+ InvariantChecker ic(this);
+
+ do {
+ va_list aq;
+ va_copy(aq, ap);
+ int i = vsnprintf(base + offset, size - offset, fmt, aq);
+ va_end(aq);
+ if (i > -1 && (size_t) i < size - offset) {
+ offset += i;
+ return i;
+ }
+ } while (realloc_(size * 2));
+
+ return -1;
+}
+
+int
+Sprinter::putString(JSString* s)
+{
+ InvariantChecker ic(this);
+
+ size_t length = s->length();
+ size_t size = length;
+
+ ptrdiff_t oldOffset = offset;
+ char* buffer = reserve(size);
+ if (!buffer)
+ return -1;
+
+ JSLinearString* linear = s->ensureLinear(context);
+ if (!linear)
+ return -1;
+
+ JS::AutoCheckCannotGC nogc;
+ if (linear->hasLatin1Chars())
+ PodCopy(reinterpret_cast<Latin1Char*>(buffer), linear->latin1Chars(nogc), length);
+ else
+ DeflateStringToBuffer(nullptr, linear->twoByteChars(nogc), length, buffer, &size);
+
+ buffer[size] = 0;
+ return oldOffset;
+}
+
+ptrdiff_t
+Sprinter::getOffset() const
+{
+ return offset;
+}
+
+void
+Sprinter::reportOutOfMemory()
+{
+ if (hadOOM_)
+ return;
+ if (context && shouldReportOOM)
+ ReportOutOfMemory(context);
+ hadOOM_ = true;
+}
+
+bool
+Sprinter::jsprintf(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+
+ UniquePtr<char, JS::FreePolicy> chars(JS_vsmprintf(format, ap)); /* XXX vsaprintf */
+ va_end(ap);
+ if (!chars) {
+ reportOutOfMemory();
+ return false;
+ }
+
+ return put(chars.get()) >= 0;
+}
+
+const char js_EscapeMap[] = {
+ '\b', 'b',
+ '\f', 'f',
+ '\n', 'n',
+ '\r', 'r',
+ '\t', 't',
+ '\v', 'v',
+ '"', '"',
+ '\'', '\'',
+ '\\', '\\',
+ '\0'
+};
+
+template <typename CharT>
+static char*
+QuoteString(Sprinter* sp, const CharT* s, size_t length, char16_t quote)
+{
+ /* Sample off first for later return value pointer computation. */
+ ptrdiff_t offset = sp->getOffset();
+
+ if (quote) {
+ if (!sp->jsprintf("%c", char(quote)))
+ return nullptr;
+ }
+
+ const CharT* end = s + length;
+
+ /* Loop control variables: end points at end of string sentinel. */
+ for (const CharT* t = s; t < end; s = ++t) {
+ /* Move t forward from s past un-quote-worthy characters. */
+ char16_t c = *t;
+ while (c < 127 && isprint(c) && c != quote && c != '\\' && c != '\t') {
+ c = *++t;
+ if (t == end)
+ break;
+ }
+
+ {
+ ptrdiff_t len = t - s;
+ ptrdiff_t base = sp->getOffset();
+ if (!sp->reserve(len))
+ return nullptr;
+
+ for (ptrdiff_t i = 0; i < len; ++i)
+ (*sp)[base + i] = char(*s++);
+ (*sp)[base + len] = 0;
+ }
+
+ if (t == end)
+ break;
+
+ /* Use js_EscapeMap, \u, or \x only if necessary. */
+ const char* escape;
+ if (!(c >> 8) && c != 0 && (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
+ if (!sp->jsprintf("\\%c", escape[1]))
+ return nullptr;
+ } 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).
+ */
+ if (!sp->jsprintf((quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c))
+ return nullptr;
+ }
+ }
+
+ /* Sprint the closing quote and return the quoted string. */
+ if (quote) {
+ if (!sp->jsprintf("%c", char(quote)))
+ return nullptr;
+ }
+
+ /*
+ * If we haven't Sprint'd anything yet, Sprint an empty string so that
+ * the return below gives a valid result.
+ */
+ if (offset == sp->getOffset()) {
+ if (sp->put("") < 0)
+ return nullptr;
+ }
+
+ return sp->stringAt(offset);
+}
+
+char*
+QuoteString(Sprinter* sp, JSString* str, char16_t quote)
+{
+ JSLinearString* linear = str->ensureLinear(sp->context);
+ if (!linear)
+ return nullptr;
+
+ JS::AutoCheckCannotGC nogc;
+ return linear->hasLatin1Chars()
+ ? QuoteString(sp, linear->latin1Chars(nogc), linear->length(), quote)
+ : QuoteString(sp, linear->twoByteChars(nogc), linear->length(), quote);
+}
+
+JSString*
+QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote)
+{
+ Sprinter sprinter(cx);
+ if (!sprinter.init())
+ return nullptr;
+ char* bytes = QuoteString(&sprinter, str, quote);
+ if (!bytes)
+ return nullptr;
+ return NewStringCopyZ<CanGC>(cx, bytes);
+}
+
+Fprinter::Fprinter(FILE* fp)
+ : file_(nullptr),
+ init_(false)
+{
+ init(fp);
+}
+
+Fprinter::Fprinter()
+ : file_(nullptr),
+ init_(false)
+{ }
+
+Fprinter::~Fprinter()
+{
+ MOZ_ASSERT_IF(init_, !file_);
+}
+
+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;
+}
+
+int
+Fprinter::put(const char* s, size_t len)
+{
+ MOZ_ASSERT(file_);
+ int i = fwrite(s, len, 1, file_);
+ if (size_t(i) != len) {
+ reportOutOfMemory();
+ return -1;
+ }
+ return i;
+}
+
+int
+Fprinter::printf(const char* fmt, ...)
+{
+ MOZ_ASSERT(file_);
+ va_list ap;
+ va_start(ap, fmt);
+ int i = vfprintf(file_, fmt, ap);
+ if (i == -1)
+ reportOutOfMemory();
+ va_end(ap);
+ return i;
+}
+
+int
+Fprinter::vprintf(const char* fmt, va_list ap)
+{
+ MOZ_ASSERT(file_);
+ int i = vfprintf(file_, fmt, ap);
+ if (i == -1)
+ reportOutOfMemory();
+ return i;
+}
+
+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;
+}
+
+int
+LSprinter::put(const char* s, size_t len)
+{
+ // 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 -1;
+ }
+ }
+
+ // 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);
+ return int(len);
+}
+
+int
+LSprinter::printf(const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int i = vprintf(fmt, va);
+ va_end(va);
+ return i;
+}
+
+int
+LSprinter::vprintf(const char* fmt, va_list ap)
+{
+ // Simple shortcut to avoid allocating strings.
+ if (strchr(fmt, '%') == nullptr)
+ return put(fmt);
+
+ char* bp;
+ bp = JS_vsmprintf(fmt, ap); /* XXX vsaprintf */
+ if (!bp) {
+ reportOutOfMemory();
+ return -1;
+ }
+ int i = put(bp);
+ js_free(bp);
+ return i;
+}
+
+void
+LSprinter::reportOutOfMemory()
+{
+ if (hadOOM_)
+ return;
+ hadOOM_ = true;
+}
+
+bool
+LSprinter::hadOutOfMemory() const
+{
+ return hadOOM_;
+}
+
+} // namespace js