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