tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Printer.cpp (19957B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "js/Printer.h"
      8 
      9 #include "mozilla/PodOperations.h"
     10 #include "mozilla/Printf.h"
     11 
     12 #include <stdarg.h>
     13 #include <stdio.h>
     14 
     15 #include "ds/LifoAlloc.h"
     16 #include "js/CharacterEncoding.h"
     17 #include "util/Memory.h"
     18 #include "util/Text.h"
     19 #include "util/WindowsWrapper.h"
     20 #include "vm/StringType.h"
     21 
     22 using mozilla::PodCopy;
     23 
     24 namespace {
     25 
     26 class GenericPrinterPrintfTarget : public mozilla::PrintfTarget {
     27 public:
     28  explicit GenericPrinterPrintfTarget(js::GenericPrinter& p) : printer(p) {}
     29 
     30  bool append(const char* sp, size_t len) override {
     31    printer.put(sp, len);
     32    return true;
     33  }
     34 
     35 private:
     36  js::GenericPrinter& printer;
     37 };
     38 
     39 }  // namespace
     40 
     41 namespace js {
     42 
     43 void GenericPrinter::setPendingOutOfMemory() {
     44  if (hadOOM_) {
     45    return;
     46  }
     47  hadOOM_ = true;
     48 }
     49 
     50 void GenericPrinter::put(mozilla::Span<const JS::Latin1Char> str) {
     51  if (!str.Length()) {
     52    return;
     53  }
     54  put(reinterpret_cast<const char*>(&str[0]), str.Length());
     55 }
     56 
     57 void GenericPrinter::put(mozilla::Span<const char16_t> str) {
     58  for (char16_t c : str) {
     59    putChar(c);
     60  }
     61 }
     62 
     63 void GenericPrinter::putString(JSContext* cx, JSString* str) {
     64  StringSegmentRange iter(cx);
     65  if (!iter.init(str)) {
     66    setPendingOutOfMemory();
     67    return;
     68  }
     69  JS::AutoCheckCannotGC nogc;
     70  while (!iter.empty()) {
     71    JSLinearString* linear = iter.front();
     72    if (linear->hasLatin1Chars()) {
     73      put(linear->latin1Range(nogc));
     74    } else {
     75      put(linear->twoByteRange(nogc));
     76    }
     77    if (!iter.popFront()) {
     78      setPendingOutOfMemory();
     79      return;
     80    }
     81  }
     82 }
     83 
     84 void GenericPrinter::printf(const char* fmt, ...) {
     85  va_list va;
     86  va_start(va, fmt);
     87  vprintf(fmt, va);
     88  va_end(va);
     89 }
     90 
     91 void GenericPrinter::vprintf(const char* fmt, va_list ap) {
     92  // Simple shortcut to avoid allocating strings.
     93  if (strchr(fmt, '%') == nullptr) {
     94    put(fmt);
     95    return;
     96  }
     97 
     98  GenericPrinterPrintfTarget printer(*this);
     99  (void)printer.vprint(fmt, ap);
    100 }
    101 
    102 const size_t StringPrinter::DefaultSize = 64;
    103 
    104 bool StringPrinter::realloc_(size_t newSize) {
    105  MOZ_ASSERT(newSize > (size_t)offset);
    106  if (hadOOM_) {
    107    return false;
    108  }
    109  char* newBuf = (char*)js_arena_realloc(arena, base, newSize);
    110  if (!newBuf) {
    111    // NOTE: The consumer of this method shouldn't directly propagate the error
    112    //       mode to the outside of this class.
    113    //       The OOM flag set here should be forwarded to the JSContext with
    114    //       the releaseChars etc.
    115    setPendingOutOfMemory();
    116    return false;
    117  }
    118  base = newBuf;
    119  size = newSize;
    120  base[size - 1] = '\0';
    121  return true;
    122 }
    123 
    124 StringPrinter::StringPrinter(arena_id_t arena, JSContext* maybeCx,
    125                             bool shouldReportOOM)
    126    : maybeCx(maybeCx),
    127 #ifdef DEBUG
    128      initialized(false),
    129 #endif
    130      shouldReportOOM(maybeCx && shouldReportOOM),
    131      base(nullptr),
    132      size(0),
    133      offset(0),
    134      arena(arena) {
    135 }
    136 
    137 StringPrinter::~StringPrinter() {
    138 #ifdef DEBUG
    139  if (initialized) {
    140    checkInvariants();
    141  }
    142 #endif
    143  js_free(base);
    144 }
    145 
    146 bool StringPrinter::init() {
    147  MOZ_ASSERT(!initialized);
    148  base = js_pod_arena_malloc<char>(arena, DefaultSize);
    149  if (!base) {
    150    setPendingOutOfMemory();
    151    forwardOutOfMemory();
    152    return false;
    153  }
    154 #ifdef DEBUG
    155  initialized = true;
    156 #endif
    157  *base = '\0';
    158  size = DefaultSize;
    159  base[size - 1] = '\0';
    160  return true;
    161 }
    162 
    163 void StringPrinter::checkInvariants() const {
    164  MOZ_ASSERT(initialized);
    165  MOZ_ASSERT((size_t)offset < size);
    166  MOZ_ASSERT(base[size - 1] == '\0');
    167 }
    168 
    169 UniqueChars StringPrinter::releaseChars() {
    170  if (hadOOM_) {
    171    forwardOutOfMemory();
    172    return nullptr;
    173  }
    174 
    175  checkInvariants();
    176  char* str = base;
    177  base = nullptr;
    178  offset = size = 0;
    179 #ifdef DEBUG
    180  initialized = false;
    181 #endif
    182  return UniqueChars(str);
    183 }
    184 
    185 JSString* StringPrinter::releaseJS(JSContext* cx) {
    186  if (hadOOM_) {
    187    MOZ_ASSERT_IF(maybeCx, maybeCx == cx);
    188    forwardOutOfMemory();
    189    return nullptr;
    190  }
    191 
    192  checkInvariants();
    193 
    194  // Extract StringPrinter data.
    195  size_t len = length();
    196  UniqueChars str(base);
    197 
    198  // Reset StringPrinter.
    199  base = nullptr;
    200  offset = 0;
    201  size = 0;
    202 #ifdef DEBUG
    203  initialized = false;
    204 #endif
    205 
    206  // Convert extracted data to a JSString, reusing the current buffer whenever
    207  // possible.
    208  JS::UTF8Chars utf8(str.get(), len);
    209  JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
    210  if (encoding == JS::SmallestEncoding::ASCII) {
    211    UniqueLatin1Chars latin1(reinterpret_cast<JS::Latin1Char*>(str.release()));
    212    return js::NewString<js::CanGC>(cx, std::move(latin1), len);
    213  }
    214 
    215  size_t length;
    216  if (encoding == JS::SmallestEncoding::Latin1) {
    217    UniqueLatin1Chars latin1(
    218        UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena)
    219            .get());
    220    if (!latin1) {
    221      return nullptr;
    222    }
    223 
    224    return js::NewString<js::CanGC>(cx, std::move(latin1), length);
    225  }
    226 
    227  MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16);
    228 
    229  UniqueTwoByteChars utf16(
    230      UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena)
    231          .get());
    232  if (!utf16) {
    233    return nullptr;
    234  }
    235 
    236  return js::NewString<js::CanGC>(cx, std::move(utf16), length);
    237 }
    238 
    239 char* StringPrinter::reserve(size_t len) {
    240  InvariantChecker ic(this);
    241 
    242  while (len + 1 > size - offset) { /* Include trailing \0 */
    243    if (!realloc_(size * 2)) {
    244      return nullptr;
    245    }
    246  }
    247 
    248  char* sb = base + offset;
    249  offset += len;
    250  return sb;
    251 }
    252 
    253 void StringPrinter::put(const char* s, size_t len) {
    254  InvariantChecker ic(this);
    255 
    256  const char* oldBase = base;
    257  const char* oldEnd = base + size;
    258 
    259  char* bp = reserve(len);
    260  if (!bp) {
    261    return;
    262  }
    263 
    264  // s is within the buffer already
    265  if (s >= oldBase && s < oldEnd) {
    266    // Update the source pointer in case of a realloc-ation.
    267    size_t index = s - oldBase;
    268    s = &base[index];
    269    memmove(bp, s, len);
    270  } else {
    271    js_memcpy(bp, s, len);
    272  }
    273 
    274  bp[len] = '\0';
    275 }
    276 
    277 void StringPrinter::putString(JSContext* cx, JSString* s) {
    278  MOZ_ASSERT(cx);
    279  InvariantChecker ic(this);
    280 
    281  JSLinearString* linear = s->ensureLinear(cx);
    282  if (!linear) {
    283    return;
    284  }
    285 
    286  size_t length = JS::GetDeflatedUTF8StringLength(linear);
    287 
    288  char* buffer = reserve(length);
    289  if (!buffer) {
    290    return;
    291  }
    292 
    293  mozilla::DebugOnly<size_t> written =
    294      JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(buffer, length));
    295  MOZ_ASSERT(written == length);
    296 
    297  buffer[length] = '\0';
    298 }
    299 
    300 size_t StringPrinter::length() const { return size_t(offset); }
    301 
    302 void StringPrinter::forwardOutOfMemory() {
    303  MOZ_ASSERT(hadOOM_);
    304  if (maybeCx && shouldReportOOM) {
    305    ReportOutOfMemory(maybeCx);
    306  }
    307 }
    308 
    309 const char js_EscapeMap[] = {
    310    // clang-format off
    311    '\b', 'b',
    312    '\f', 'f',
    313    '\n', 'n',
    314    '\r', 'r',
    315    '\t', 't',
    316    '\v', 'v',
    317    '"',  '"',
    318    '\'', '\'',
    319    '\\', '\\',
    320    '\0'
    321    // clang-format on
    322 };
    323 
    324 static const char JSONEscapeMap[] = {
    325    // clang-format off
    326    '\b', 'b',
    327    '\f', 'f',
    328    '\n', 'n',
    329    '\r', 'r',
    330    '\t', 't',
    331    '"',  '"',
    332    '\\', '\\',
    333    '\0'
    334    // clang-format on
    335 };
    336 
    337 static const char WATEscapeMap[] = {
    338    // clang-format off
    339    '\t', 't',
    340    '\n', 'n',
    341    '\r', 'r',
    342    '"',  '"',
    343    '\'',  '\'',
    344    '\\', '\\',
    345    '\0'
    346    // clang-format on
    347 };
    348 
    349 template <QuoteTarget target, typename CharT>
    350 JS_PUBLIC_API void QuoteString(Sprinter* sp,
    351                               const mozilla::Range<const CharT>& chars,
    352                               char quote) {
    353  MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0');
    354 
    355  if (quote) {
    356    sp->putChar(quote);
    357  }
    358  if (target == QuoteTarget::String) {
    359    StringEscape esc(quote);
    360    EscapePrinter ep(*sp, esc);
    361    ep.put(chars);
    362  } else {
    363    MOZ_ASSERT(target == QuoteTarget::JSON);
    364    JSONEscape esc;
    365    EscapePrinter ep(*sp, esc);
    366    ep.put(chars);
    367  }
    368  if (quote) {
    369    sp->putChar(quote);
    370  }
    371 }
    372 
    373 template JS_PUBLIC_API void QuoteString<QuoteTarget::String, Latin1Char>(
    374    Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
    375 
    376 template JS_PUBLIC_API void QuoteString<QuoteTarget::String, char16_t>(
    377    Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
    378 
    379 template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, Latin1Char>(
    380    Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
    381 
    382 template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, char16_t>(
    383    Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
    384 
    385 JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str,
    386                               char quote /*= '\0' */) {
    387  MOZ_ASSERT(sp->maybeCx);
    388  if (quote) {
    389    sp->putChar(quote);
    390  }
    391  StringEscape esc(quote);
    392  EscapePrinter ep(*sp, esc);
    393  ep.putString(sp->maybeCx, str);
    394  if (quote) {
    395    sp->putChar(quote);
    396  }
    397 }
    398 
    399 JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str,
    400                                      char quote /* = '\0' */) {
    401  Sprinter sprinter(cx);
    402  if (!sprinter.init()) {
    403    return nullptr;
    404  }
    405  QuoteString(&sprinter, str, quote);
    406  return sprinter.release();
    407 }
    408 
    409 JS_PUBLIC_API void JSONQuoteString(StringPrinter* sp, JSString* str) {
    410  MOZ_ASSERT(sp->maybeCx);
    411  JSONEscape esc;
    412  EscapePrinter ep(*sp, esc);
    413  ep.putString(sp->maybeCx, str);
    414 }
    415 
    416 Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); }
    417 
    418 #ifdef DEBUG
    419 Fprinter::~Fprinter() { MOZ_ASSERT_IF(init_, !file_); }
    420 #endif
    421 
    422 bool Fprinter::init(const char* path) {
    423  MOZ_ASSERT(!file_);
    424  file_ = fopen(path, "w");
    425  if (!file_) {
    426    return false;
    427  }
    428  init_ = true;
    429  return true;
    430 }
    431 
    432 void Fprinter::init(FILE* fp) {
    433  MOZ_ASSERT(!file_);
    434  file_ = fp;
    435  init_ = false;
    436 }
    437 
    438 void Fprinter::flush() {
    439  MOZ_ASSERT(file_);
    440  fflush(file_);
    441 }
    442 
    443 void Fprinter::finish() {
    444  MOZ_ASSERT(file_);
    445  if (init_) {
    446    fclose(file_);
    447  }
    448  file_ = nullptr;
    449 }
    450 
    451 void Fprinter::put(const char* s, size_t len) {
    452  if (hadOutOfMemory()) {
    453    return;
    454  }
    455 
    456  MOZ_ASSERT(file_);
    457  int i = fwrite(s, /*size=*/1, /*nitems=*/len, file_);
    458  if (size_t(i) != len) {
    459    setPendingOutOfMemory();
    460    return;
    461  }
    462 #ifdef XP_WIN
    463  if ((file_ == stderr) && (IsDebuggerPresent())) {
    464    UniqueChars buf = DuplicateString(s, len);
    465    if (!buf) {
    466      setPendingOutOfMemory();
    467      return;
    468    }
    469    OutputDebugStringA(buf.get());
    470  }
    471 #endif
    472 }
    473 
    474 LSprinter::LSprinter(LifoAlloc* lifoAlloc)
    475    : alloc_(lifoAlloc), head_(nullptr), tail_(nullptr), unused_(0) {}
    476 
    477 LSprinter::~LSprinter() {
    478  // This LSprinter might be allocated as part of the same LifoAlloc, so we
    479  // should not expect the destructor to be called.
    480 }
    481 
    482 void LSprinter::exportInto(GenericPrinter& out) const {
    483  if (!head_) {
    484    return;
    485  }
    486 
    487  for (Chunk* it = head_; it != tail_; it = it->next) {
    488    out.put(it->chars(), it->length);
    489  }
    490  out.put(tail_->chars(), tail_->length - unused_);
    491 }
    492 
    493 void LSprinter::clear() {
    494  head_ = nullptr;
    495  tail_ = nullptr;
    496  unused_ = 0;
    497  hadOOM_ = false;
    498 }
    499 
    500 void FixedBufferPrinter::put(const char* s, size_t len) {
    501  snprintf(buffer_, size_, "%.*s", int(len), s);
    502  size_t written = std::min(len, size_);
    503  MOZ_ASSERT(size_ >= written);
    504  size_ -= written;
    505  buffer_ += written;
    506 }
    507 
    508 void LSprinter::put(const char* s, size_t len) {
    509  if (hadOutOfMemory()) {
    510    return;
    511  }
    512 
    513  // Compute how much data will fit in the current chunk.
    514  size_t existingSpaceWrite = 0;
    515  size_t overflow = len;
    516  if (unused_ > 0 && tail_) {
    517    existingSpaceWrite = std::min(unused_, len);
    518    overflow = len - existingSpaceWrite;
    519  }
    520 
    521  // If necessary, allocate a new chunk for overflow data.
    522  size_t allocLength = 0;
    523  Chunk* last = nullptr;
    524  if (overflow > 0) {
    525    allocLength =
    526        AlignBytes(sizeof(Chunk) + overflow, js::detail::LIFO_ALLOC_ALIGN);
    527 
    528    LifoAlloc::AutoFallibleScope fallibleAllocator(alloc_);
    529    last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength));
    530    if (!last) {
    531      setPendingOutOfMemory();
    532      return;
    533    }
    534  }
    535 
    536  // All fallible operations complete: now fill up existing space, then
    537  // overflow space in any new chunk.
    538  MOZ_ASSERT(existingSpaceWrite + overflow == len);
    539 
    540  if (existingSpaceWrite > 0) {
    541    PodCopy(tail_->end() - unused_, s, existingSpaceWrite);
    542    unused_ -= existingSpaceWrite;
    543    s += existingSpaceWrite;
    544  }
    545 
    546  if (overflow > 0) {
    547    if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) {
    548      // tail_ and last are consecutive in memory.  LifoAlloc has no
    549      // metadata and is just a bump allocator, so we can cheat by
    550      // appending the newly-allocated space to tail_.
    551      unused_ = allocLength;
    552      tail_->length += allocLength;
    553    } else {
    554      // Remove the size of the header from the allocated length.
    555      size_t availableSpace = allocLength - sizeof(Chunk);
    556      last->next = nullptr;
    557      last->length = availableSpace;
    558 
    559      unused_ = availableSpace;
    560      if (!head_) {
    561        head_ = last;
    562      } else {
    563        tail_->next = last;
    564      }
    565 
    566      tail_ = last;
    567    }
    568 
    569    PodCopy(tail_->end() - unused_, s, overflow);
    570 
    571    MOZ_ASSERT(unused_ >= overflow);
    572    unused_ -= overflow;
    573  }
    574 
    575  MOZ_ASSERT(len <= INT_MAX);
    576 }
    577 
    578 bool JSONEscape::isSafeChar(char16_t c) {
    579  return js::IsAsciiPrintable(c) && c != '"' && c != '\\';
    580 }
    581 
    582 void JSONEscape::convertInto(GenericPrinter& out, char16_t c) {
    583  const char* escape = nullptr;
    584  if (!(c >> 8) && c != 0 &&
    585      (escape = strchr(JSONEscapeMap, int(c))) != nullptr) {
    586    out.printf("\\%c", escape[1]);
    587  } else {
    588    out.printf("\\u%04X", c);
    589  }
    590 }
    591 
    592 bool StringEscape::isSafeChar(char16_t c) {
    593  return js::IsAsciiPrintable(c) && c != quote && c != '\\';
    594 }
    595 
    596 void StringEscape::convertInto(GenericPrinter& out, char16_t c) {
    597  const char* escape = nullptr;
    598  if (!(c >> 8) && c != 0 &&
    599      (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
    600    out.printf("\\%c", escape[1]);
    601  } else {
    602    // Use \x only if the high byte is 0 and we're in a quoted string, because
    603    // ECMA-262 allows only \u, not \x, in Unicode identifiers (see bug 621814).
    604    out.printf(!(c >> 8) ? "\\x%02X" : "\\u%04X", c);
    605  }
    606 }
    607 
    608 bool WATStringEscape::isSafeChar(char16_t c) {
    609  return js::IsAsciiPrintable(c) && c != '"' && c != '\\';
    610 }
    611 
    612 void WATStringEscape::convertInto(GenericPrinter& out, char16_t c) {
    613  const char* escape = nullptr;
    614  if (!(c >> 8) && c != 0 &&
    615      (escape = strchr(WATEscapeMap, int(c))) != nullptr) {
    616    out.printf("\\%c", escape[1]);
    617  } else {
    618    out.printf("\\%02X", c);
    619  }
    620 }
    621 
    622 void StructuredPrinter::pushScope() {
    623  if (hadOutOfMemory()) {
    624    return;
    625  }
    626 
    627  bool ok = scopes_.append((ScopeInfo){
    628      .startPos = uint32_t(buffer_.length()),
    629      .indent = scopeDepth() + 1,
    630  });
    631  if (!ok) {
    632    setPendingOutOfMemory();
    633  }
    634 }
    635 
    636 void StructuredPrinter::popScope() {
    637  // If we previously ran out of memory, we may have failed to push a scope, and
    638  // this logic may fail.
    639  if (hadOutOfMemory()) {
    640    return;
    641  }
    642 
    643  // If the scope remained collapsed, flag all its breaks as collapsed.
    644  if (!isExpanded()) {
    645    const ScopeInfo& scope = scopes_.back();
    646    for (Break& brk : breaks_) {
    647      if (scope.startPos <= brk.bufferPos) {
    648        brk.isCollapsed = true;
    649      }
    650    }
    651  }
    652 
    653  scopes_.popBack();
    654  if (scopeDepth() < expandedDepth_) {
    655    expandedDepth_ = scopeDepth();
    656  }
    657 
    658  // If we are back to a depth where we are printing expanded, flush the buffer
    659  // to get back into our expected expanded state.
    660  if (isExpanded()) {
    661    flush();
    662  }
    663 }
    664 
    665 void StructuredPrinter::flush() {
    666  uint32_t cursor = 0;
    667 
    668  // Output any initial breaks.
    669  while (!breaks_.empty() && breaks_[0].bufferPos == 0) {
    670    putBreak(breaks_[0]);
    671    breaks_.erase(&breaks_[0]);
    672  }
    673 
    674  // Output all chunks from the buffer, split by scopes and breaks.
    675  while (cursor < buffer_.length()) {
    676    int indent = 0;
    677    mozilla::Maybe<const ScopeInfo&> nextScope;
    678    for (const ScopeInfo& scope : scopes_) {
    679      if (cursor < scope.startPos) {
    680        nextScope.emplace(scope);
    681        break;
    682      }
    683      indent = scope.indent;
    684    }
    685 
    686    // Find the next break(s) or start of the next scope.
    687    size_t len = buffer_.length() - cursor;
    688    mozilla::Maybe<size_t> nextBreaksIndex;
    689    size_t numBreaks = 0;
    690    for (size_t i = 0; i < breaks_.length(); i++) {
    691      const Break& brk = breaks_[i];
    692      if (nextScope.isSome() && nextScope->startPos < brk.bufferPos) {
    693        len = nextScope->startPos - cursor;
    694        break;
    695      }
    696      if (cursor < brk.bufferPos) {
    697        len = brk.bufferPos - cursor;
    698        nextBreaksIndex.emplace(i);
    699        // There can be more than one break at the same cursor position;
    700        // grab them all.
    701        for (size_t j = i; j < breaks_.length(); j++) {
    702          if (breaks_[i].bufferPos == breaks_[j].bufferPos) {
    703            numBreaks += 1;
    704          }
    705        }
    706        break;
    707      }
    708    }
    709 
    710    putWithMaybeIndent(&buffer_[cursor], len, indent);
    711    cursor += len;
    712 
    713    if (nextBreaksIndex.isSome()) {
    714      for (size_t i = 0; i < numBreaks; i++) {
    715        putBreak(breaks_[nextBreaksIndex.value() + i]);
    716      }
    717    }
    718  }
    719 
    720  // Clear the buffer, clear all breaks, and reset scope positions.
    721  buffer_.clear();
    722  breaks_.clear();
    723  for (ScopeInfo& scope : scopes_) {
    724    scope.startPos = 0;
    725  }
    726 }
    727 
    728 void StructuredPrinter::brk(const char* whenCollapsed,
    729                            const char* whenExpanded) {
    730  Break b = (Break){
    731      .bufferPos = uint32_t(buffer_.length()),
    732      .collapsed = whenCollapsed,
    733      .expanded = whenExpanded,
    734  };
    735  if (isExpanded()) {
    736    putBreak(b);
    737  } else {
    738    bool ok = breaks_.append(b);
    739    if (!ok) {
    740      setPendingOutOfMemory();
    741    }
    742  }
    743 }
    744 
    745 void StructuredPrinter::expand() {
    746  expandedDepth_ = scopeDepth();
    747  flush();
    748 }
    749 
    750 bool StructuredPrinter::isExpanded() { return expandedDepth_ == scopeDepth(); }
    751 
    752 void StructuredPrinter::putIndent(int level) {
    753  // Allocate a static buffer of 16 spaces (plus null terminator) and use that
    754  // in batches for the total number of spaces we need to put.
    755  static const char spaceBuffer[17] = "                ";
    756 
    757  if (level < 0) {
    758    level = scopeDepth();
    759  }
    760  size_t remainingSpaces = level * indentAmount_;
    761  while (remainingSpaces > 16) {
    762    out_.put(spaceBuffer, 16);
    763    remainingSpaces -= 16;
    764  }
    765  if (remainingSpaces) {
    766    out_.put(spaceBuffer, remainingSpaces);
    767  }
    768 }
    769 
    770 void StructuredPrinter::putBreak(const Break& brk) {
    771  const char* s = brk.isCollapsed ? brk.collapsed : brk.expanded;
    772  size_t len = strlen(s);
    773  if (len > 0) {
    774    out_.put(s, len);
    775    pendingIndent_ = s[len - 1] == '\n';
    776  }
    777 }
    778 
    779 void StructuredPrinter::putWithMaybeIndent(const char* s, size_t len,
    780                                           int level) {
    781  if (len == 0) {
    782    return;
    783  }
    784  if (pendingIndent_) {
    785    putIndent(level);
    786    pendingIndent_ = false;
    787  }
    788  out_.put(s, len);
    789 }
    790 
    791 void StructuredPrinter::put(const char* s, size_t len) {
    792  const char* current = s;
    793 
    794  // Split the text into lines and output each with the appropriate indent
    795  while (const char* nextLineEnd = (const char*)memchr(current, '\n', len)) {
    796    // Newlines mean we must expand.
    797    expand();
    798 
    799    // For the rest of this loop we can assume that we are expanded and buffer
    800    // nothing.
    801 
    802    // Put this line (including the newline)
    803    size_t lineWithNewLineSize = nextLineEnd - current + 1;
    804    putWithMaybeIndent(current, lineWithNewLineSize);
    805 
    806    // The next put should have an indent added
    807    pendingIndent_ = true;
    808 
    809    // Advance the cursor
    810    current += lineWithNewLineSize;
    811    len -= lineWithNewLineSize;
    812  }
    813 
    814  // The rest of the text is newline-free. We buffer it if we are collapsed, and
    815  // output it directly if expanded.
    816  if (isExpanded()) {
    817    putWithMaybeIndent(current, len);
    818  } else {
    819    if (!buffer_.append(current, len)) {
    820      setPendingOutOfMemory();
    821    }
    822  }
    823 }
    824 
    825 }  // namespace js