| /* -*- 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 <ctype.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| |
| #include "jscntxt.h" |
| #include "jsprf.h" |
| #include "jsutil.h" |
| #include "js-confdefs.h" |
| |
| #include "ds/LifoAlloc.h" |
| |
| namespace js { |
| |
| GenericPrinter::GenericPrinter() |
| : hadOOM_(false) |
| { |
| } |
| |
| void |
| GenericPrinter::reportOutOfMemory() |
| { |
| if (hadOOM_) |
| return; |
| hadOOM_ = true; |
| } |
| |
| bool |
| GenericPrinter::hadOutOfMemory() const |
| { |
| return hadOOM_; |
| } |
| |
| int |
| GenericPrinter::put(const char* s) |
| { |
| return put(s, strlen(s)); |
| } |
| |
| 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 = JS_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()) |
| mozilla::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; |
| } |
| |
| ptrdiff_t |
| Sprint(Sprinter* sp, const char* format, ...) |
| { |
| va_list ap; |
| char* bp; |
| ptrdiff_t offset; |
| |
| va_start(ap, format); |
| bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ |
| va_end(ap); |
| if (!bp) { |
| sp->reportOutOfMemory(); |
| return -1; |
| } |
| offset = sp->put(bp); |
| js_free(bp); |
| return offset; |
| } |
| |
| 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 && Sprint(sp, "%c", char(quote)) < 0) |
| 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 (Sprint(sp, "\\%c", escape[1]) < 0) |
| 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 (Sprint(sp, (quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) < 0) |
| return nullptr; |
| } |
| } |
| |
| /* Sprint the closing quote and return the quoted string. */ |
| if (quote && Sprint(sp, "%c", char(quote)) < 0) |
| 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() && Sprint(sp, "") < 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 (i == -1 || i != int(len)) |
| reportOutOfMemory(); |
| return i; |
| } |
| |
| int |
| Fprinter::put(const char* s) |
| { |
| MOZ_ASSERT(file_); |
| int i = fputs(s, file_); |
| if (i == -1) |
| reportOutOfMemory(); |
| 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) |
| { |
| size_t origLen = len; |
| if (unused_ > 0 && tail_) { |
| size_t minLen = unused_ < len ? unused_ : len; |
| js_memcpy(tail_->end() - unused_, s, minLen); |
| unused_ -= minLen; |
| len -= minLen; |
| s += minLen; |
| } |
| |
| if (len == 0) |
| return origLen; |
| |
| size_t allocLength = AlignBytes(sizeof(Chunk) + len, js::detail::LIFO_ALLOC_ALIGN); |
| Chunk* last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength)); |
| if (!last) { |
| reportOutOfMemory(); |
| return origLen - len; |
| } |
| |
| if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) { |
| // tail_ and last are next to each others in memory, knowing that the |
| // TempAlloctator has no meta data and is just a bump allocator, we |
| // append the new allocated space to the tail_. |
| unused_ = allocLength; |
| tail_->length += allocLength; |
| } else { |
| // Remove the size of the header from the allocated length. |
| allocLength -= sizeof(Chunk); |
| last->next = nullptr; |
| last->length = allocLength; |
| unused_ = allocLength; |
| if (!head_) |
| head_ = last; |
| else |
| tail_->next = last; |
| |
| tail_ = last; |
| } |
| |
| MOZ_ASSERT(tail_->length >= unused_); |
| js_memcpy(tail_->end() - unused_, s, len); |
| unused_ -= len; |
| return origLen; |
| } |
| |
| int |
| LSprinter::put(const char* s) |
| { |
| return put(s, strlen(s)); |
| } |
| |
| 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 |