diff options
Diffstat (limited to 'js/src/vm/Printer.cpp')
-rw-r--r-- | js/src/vm/Printer.cpp | 618 |
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 |