Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / Printer.cpp
blob614273eee896f6b5cad57ba4351da1ed7f623392
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/. */
7 #include "js/Printer.h"
9 #include "mozilla/PodOperations.h"
10 #include "mozilla/Printf.h"
11 #include "mozilla/RangedPtr.h"
13 #include <stdarg.h>
14 #include <stdio.h>
16 #include "ds/LifoAlloc.h"
17 #include "js/CharacterEncoding.h"
18 #include "util/Memory.h"
19 #include "util/Text.h"
20 #include "util/WindowsWrapper.h"
21 #include "vm/StringType.h"
23 using mozilla::PodCopy;
25 namespace {
27 class GenericPrinterPrintfTarget : public mozilla::PrintfTarget {
28 public:
29 explicit GenericPrinterPrintfTarget(js::GenericPrinter& p) : printer(p) {}
31 bool append(const char* sp, size_t len) override {
32 printer.put(sp, len);
33 return true;
36 private:
37 js::GenericPrinter& printer;
40 } // namespace
42 namespace js {
44 void GenericPrinter::reportOutOfMemory() {
45 if (hadOOM_) {
46 return;
48 hadOOM_ = true;
51 void GenericPrinter::put(mozilla::Span<const JS::Latin1Char> str) {
52 if (!str.Length()) {
53 return;
55 put(reinterpret_cast<const char*>(&str[0]), str.Length());
58 void GenericPrinter::put(mozilla::Span<const char16_t> str) {
59 for (char16_t c : str) {
60 putChar(c);
64 void GenericPrinter::putString(JSContext* cx, JSString* str) {
65 StringSegmentRange iter(cx);
66 if (!iter.init(str)) {
67 reportOutOfMemory();
68 return;
70 JS::AutoCheckCannotGC nogc;
71 while (!iter.empty()) {
72 JSLinearString* linear = iter.front();
73 if (linear->hasLatin1Chars()) {
74 put(linear->latin1Range(nogc));
75 } else {
76 put(linear->twoByteRange(nogc));
78 if (!iter.popFront()) {
79 reportOutOfMemory();
80 return;
85 void GenericPrinter::printf(const char* fmt, ...) {
86 va_list va;
87 va_start(va, fmt);
88 vprintf(fmt, va);
89 va_end(va);
92 void GenericPrinter::vprintf(const char* fmt, va_list ap) {
93 // Simple shortcut to avoid allocating strings.
94 if (strchr(fmt, '%') == nullptr) {
95 put(fmt);
96 return;
99 GenericPrinterPrintfTarget printer(*this);
100 (void)printer.vprint(fmt, ap);
103 const size_t StringPrinter::DefaultSize = 64;
105 bool StringPrinter::realloc_(size_t newSize) {
106 MOZ_ASSERT(newSize > (size_t)offset);
107 if (hadOOM_) {
108 return false;
110 char* newBuf = (char*)js_arena_realloc(arena, base, newSize);
111 if (!newBuf) {
112 reportOutOfMemory();
113 return false;
115 base = newBuf;
116 size = newSize;
117 base[size - 1] = '\0';
118 return true;
121 StringPrinter::StringPrinter(arena_id_t arena, JSContext* maybeCx,
122 bool shouldReportOOM)
123 : maybeCx(maybeCx),
124 #ifdef DEBUG
125 initialized(false),
126 #endif
127 shouldReportOOM(maybeCx && shouldReportOOM),
128 base(nullptr),
129 size(0),
130 offset(0),
131 arena(arena) {
134 StringPrinter::~StringPrinter() {
135 #ifdef DEBUG
136 if (initialized) {
137 checkInvariants();
139 #endif
140 js_free(base);
143 bool StringPrinter::init() {
144 MOZ_ASSERT(!initialized);
145 base = js_pod_arena_malloc<char>(arena, DefaultSize);
146 if (!base) {
147 reportOutOfMemory();
148 forwardOutOfMemory();
149 return false;
151 #ifdef DEBUG
152 initialized = true;
153 #endif
154 *base = '\0';
155 size = DefaultSize;
156 base[size - 1] = '\0';
157 return true;
160 void StringPrinter::checkInvariants() const {
161 MOZ_ASSERT(initialized);
162 MOZ_ASSERT((size_t)offset < size);
163 MOZ_ASSERT(base[size - 1] == '\0');
166 UniqueChars StringPrinter::releaseChars() {
167 if (hadOOM_) {
168 forwardOutOfMemory();
169 return nullptr;
172 checkInvariants();
173 char* str = base;
174 base = nullptr;
175 offset = size = 0;
176 #ifdef DEBUG
177 initialized = false;
178 #endif
179 return UniqueChars(str);
182 JSString* StringPrinter::releaseJS(JSContext* cx) {
183 if (hadOOM_) {
184 MOZ_ASSERT_IF(maybeCx, maybeCx == cx);
185 forwardOutOfMemory();
186 return nullptr;
189 checkInvariants();
191 // Extract StringPrinter data.
192 size_t len = length();
193 UniqueChars str(base);
195 // Reset StringPrinter.
196 base = nullptr;
197 offset = 0;
198 size = 0;
199 #ifdef DEBUG
200 initialized = false;
201 #endif
203 // Convert extracted data to a JSString, reusing the current buffer whenever
204 // possible.
205 JS::UTF8Chars utf8(str.get(), len);
206 JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
207 if (encoding == JS::SmallestEncoding::ASCII) {
208 UniqueLatin1Chars latin1(reinterpret_cast<JS::Latin1Char*>(str.release()));
209 return js::NewString<js::CanGC>(cx, std::move(latin1), len);
212 size_t length;
213 if (encoding == JS::SmallestEncoding::Latin1) {
214 UniqueLatin1Chars latin1(
215 UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena)
216 .get());
217 if (!latin1) {
218 return nullptr;
221 return js::NewString<js::CanGC>(cx, std::move(latin1), length);
224 MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16);
226 UniqueTwoByteChars utf16(
227 UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena)
228 .get());
229 if (!utf16) {
230 return nullptr;
233 return js::NewString<js::CanGC>(cx, std::move(utf16), length);
236 char* StringPrinter::reserve(size_t len) {
237 InvariantChecker ic(this);
239 while (len + 1 > size - offset) { /* Include trailing \0 */
240 if (!realloc_(size * 2)) {
241 return nullptr;
245 char* sb = base + offset;
246 offset += len;
247 return sb;
250 void StringPrinter::put(const char* s, size_t len) {
251 InvariantChecker ic(this);
253 const char* oldBase = base;
254 const char* oldEnd = base + size;
256 char* bp = reserve(len);
257 if (!bp) {
258 return;
261 // s is within the buffer already
262 if (s >= oldBase && s < oldEnd) {
263 // Update the source pointer in case of a realloc-ation.
264 size_t index = s - oldBase;
265 s = &base[index];
266 memmove(bp, s, len);
267 } else {
268 js_memcpy(bp, s, len);
271 bp[len] = '\0';
274 void StringPrinter::putString(JSContext* cx, JSString* s) {
275 MOZ_ASSERT(cx);
276 InvariantChecker ic(this);
278 JSLinearString* linear = s->ensureLinear(cx);
279 if (!linear) {
280 return;
283 size_t length = JS::GetDeflatedUTF8StringLength(linear);
285 char* buffer = reserve(length);
286 if (!buffer) {
287 return;
290 mozilla::DebugOnly<size_t> written =
291 JS::DeflateStringToUTF8Buffer(linear, mozilla::Span(buffer, length));
292 MOZ_ASSERT(written == length);
294 buffer[length] = '\0';
297 size_t StringPrinter::length() const { return size_t(offset); }
299 void StringPrinter::forwardOutOfMemory() {
300 MOZ_ASSERT(hadOOM_);
301 if (maybeCx && shouldReportOOM) {
302 ReportOutOfMemory(maybeCx);
306 const char js_EscapeMap[] = {
307 // clang-format off
308 '\b', 'b',
309 '\f', 'f',
310 '\n', 'n',
311 '\r', 'r',
312 '\t', 't',
313 '\v', 'v',
314 '"', '"',
315 '\'', '\'',
316 '\\', '\\',
317 '\0'
318 // clang-format on
321 static const char JSONEscapeMap[] = {
322 // clang-format off
323 '\b', 'b',
324 '\f', 'f',
325 '\n', 'n',
326 '\r', 'r',
327 '\t', 't',
328 '"', '"',
329 '\\', '\\',
330 '\0'
331 // clang-format on
334 template <QuoteTarget target, typename CharT>
335 JS_PUBLIC_API void QuoteString(Sprinter* sp,
336 const mozilla::Range<const CharT>& chars,
337 char quote) {
338 MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0');
340 if (quote) {
341 sp->putChar(quote);
343 if (target == QuoteTarget::String) {
344 StringEscape esc(quote);
345 EscapePrinter ep(*sp, esc);
346 ep.put(chars);
347 } else {
348 MOZ_ASSERT(target == QuoteTarget::JSON);
349 JSONEscape esc;
350 EscapePrinter ep(*sp, esc);
351 ep.put(chars);
353 if (quote) {
354 sp->putChar(quote);
358 template JS_PUBLIC_API void QuoteString<QuoteTarget::String, Latin1Char>(
359 Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
361 template JS_PUBLIC_API void QuoteString<QuoteTarget::String, char16_t>(
362 Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
364 template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, Latin1Char>(
365 Sprinter* sp, const mozilla::Range<const Latin1Char>& chars, char quote);
367 template JS_PUBLIC_API void QuoteString<QuoteTarget::JSON, char16_t>(
368 Sprinter* sp, const mozilla::Range<const char16_t>& chars, char quote);
370 JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str,
371 char quote /*= '\0' */) {
372 MOZ_ASSERT(sp->maybeCx);
373 if (quote) {
374 sp->putChar(quote);
376 StringEscape esc(quote);
377 EscapePrinter ep(*sp, esc);
378 ep.putString(sp->maybeCx, str);
379 if (quote) {
380 sp->putChar(quote);
384 JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str,
385 char quote /* = '\0' */) {
386 Sprinter sprinter(cx);
387 if (!sprinter.init()) {
388 return nullptr;
390 QuoteString(&sprinter, str, quote);
391 return sprinter.release();
394 JS_PUBLIC_API void JSONQuoteString(StringPrinter* sp, JSString* str) {
395 MOZ_ASSERT(sp->maybeCx);
396 JSONEscape esc;
397 EscapePrinter ep(*sp, esc);
398 ep.putString(sp->maybeCx, str);
401 Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); }
403 #ifdef DEBUG
404 Fprinter::~Fprinter() { MOZ_ASSERT_IF(init_, !file_); }
405 #endif
407 bool Fprinter::init(const char* path) {
408 MOZ_ASSERT(!file_);
409 file_ = fopen(path, "w");
410 if (!file_) {
411 return false;
413 init_ = true;
414 return true;
417 void Fprinter::init(FILE* fp) {
418 MOZ_ASSERT(!file_);
419 file_ = fp;
420 init_ = false;
423 void Fprinter::flush() {
424 MOZ_ASSERT(file_);
425 fflush(file_);
428 void Fprinter::finish() {
429 MOZ_ASSERT(file_);
430 if (init_) {
431 fclose(file_);
433 file_ = nullptr;
436 void Fprinter::put(const char* s, size_t len) {
437 if (hadOutOfMemory()) {
438 return;
441 MOZ_ASSERT(file_);
442 int i = fwrite(s, /*size=*/1, /*nitems=*/len, file_);
443 if (size_t(i) != len) {
444 reportOutOfMemory();
445 return;
447 #ifdef XP_WIN
448 if ((file_ == stderr) && (IsDebuggerPresent())) {
449 UniqueChars buf = DuplicateString(s, len);
450 if (!buf) {
451 reportOutOfMemory();
452 return;
454 OutputDebugStringA(buf.get());
456 #endif
459 LSprinter::LSprinter(LifoAlloc* lifoAlloc)
460 : alloc_(lifoAlloc), head_(nullptr), tail_(nullptr), unused_(0) {}
462 LSprinter::~LSprinter() {
463 // This LSprinter might be allocated as part of the same LifoAlloc, so we
464 // should not expect the destructor to be called.
467 void LSprinter::exportInto(GenericPrinter& out) const {
468 if (!head_) {
469 return;
472 for (Chunk* it = head_; it != tail_; it = it->next) {
473 out.put(it->chars(), it->length);
475 out.put(tail_->chars(), tail_->length - unused_);
478 void LSprinter::clear() {
479 head_ = nullptr;
480 tail_ = nullptr;
481 unused_ = 0;
482 hadOOM_ = false;
485 void LSprinter::put(const char* s, size_t len) {
486 if (hadOutOfMemory()) {
487 return;
490 // Compute how much data will fit in the current chunk.
491 size_t existingSpaceWrite = 0;
492 size_t overflow = len;
493 if (unused_ > 0 && tail_) {
494 existingSpaceWrite = std::min(unused_, len);
495 overflow = len - existingSpaceWrite;
498 // If necessary, allocate a new chunk for overflow data.
499 size_t allocLength = 0;
500 Chunk* last = nullptr;
501 if (overflow > 0) {
502 allocLength =
503 AlignBytes(sizeof(Chunk) + overflow, js::detail::LIFO_ALLOC_ALIGN);
505 LifoAlloc::AutoFallibleScope fallibleAllocator(alloc_);
506 last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength));
507 if (!last) {
508 reportOutOfMemory();
509 return;
513 // All fallible operations complete: now fill up existing space, then
514 // overflow space in any new chunk.
515 MOZ_ASSERT(existingSpaceWrite + overflow == len);
517 if (existingSpaceWrite > 0) {
518 PodCopy(tail_->end() - unused_, s, existingSpaceWrite);
519 unused_ -= existingSpaceWrite;
520 s += existingSpaceWrite;
523 if (overflow > 0) {
524 if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) {
525 // tail_ and last are consecutive in memory. LifoAlloc has no
526 // metadata and is just a bump allocator, so we can cheat by
527 // appending the newly-allocated space to tail_.
528 unused_ = allocLength;
529 tail_->length += allocLength;
530 } else {
531 // Remove the size of the header from the allocated length.
532 size_t availableSpace = allocLength - sizeof(Chunk);
533 last->next = nullptr;
534 last->length = availableSpace;
536 unused_ = availableSpace;
537 if (!head_) {
538 head_ = last;
539 } else {
540 tail_->next = last;
543 tail_ = last;
546 PodCopy(tail_->end() - unused_, s, overflow);
548 MOZ_ASSERT(unused_ >= overflow);
549 unused_ -= overflow;
552 MOZ_ASSERT(len <= INT_MAX);
555 bool JSONEscape::isSafeChar(char16_t c) {
556 return js::IsAsciiPrintable(c) && c != '"' && c != '\\';
559 void JSONEscape::convertInto(GenericPrinter& out, char16_t c) {
560 const char* escape = nullptr;
561 if (!(c >> 8) && c != 0 &&
562 (escape = strchr(JSONEscapeMap, int(c))) != nullptr) {
563 out.printf("\\%c", escape[1]);
564 } else {
565 out.printf("\\u%04X", c);
569 bool StringEscape::isSafeChar(char16_t c) {
570 return js::IsAsciiPrintable(c) && c != quote && c != '\\';
573 void StringEscape::convertInto(GenericPrinter& out, char16_t c) {
574 const char* escape = nullptr;
575 if (!(c >> 8) && c != 0 &&
576 (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
577 out.printf("\\%c", escape[1]);
578 } else {
579 // Use \x only if the high byte is 0 and we're in a quoted string, because
580 // ECMA-262 allows only \u, not \x, in Unicode identifiers (see bug 621814).
581 out.printf(!(c >> 8) ? "\\x%02X" : "\\u%04X", c);
585 void IndentedPrinter::putIndent() {
586 // Allocate a static buffer of 16 spaces (plus null terminator) and use that
587 // in batches for the total number of spaces we need to put.
588 static const char spaceBuffer[17] = " ";
589 size_t remainingSpaces = indentLevel_ * indentAmount_;
590 while (remainingSpaces > 16) {
591 out_.put(spaceBuffer, 16);
592 remainingSpaces -= 16;
594 if (remainingSpaces) {
595 out_.put(spaceBuffer, remainingSpaces);
599 void IndentedPrinter::putWithMaybeIndent(const char* s, size_t len) {
600 if (len == 0) {
601 return;
603 if (pendingIndent_) {
604 putIndent();
605 pendingIndent_ = false;
607 out_.put(s, len);
610 void IndentedPrinter::put(const char* s, size_t len) {
611 const char* current = s;
612 // Split the text into lines and output each with an indent
613 while (const char* nextLineEnd = (const char*)memchr(current, '\n', len)) {
614 // Put this line (including the new-line)
615 size_t lineWithNewLineSize = nextLineEnd - current + 1;
616 putWithMaybeIndent(current, lineWithNewLineSize);
618 // The next put should have an indent added
619 pendingIndent_ = true;
621 // Advance the cursor
622 current += lineWithNewLineSize;
623 len -= lineWithNewLineSize;
626 // Put any remaining text
627 putWithMaybeIndent(current, len);
630 } // namespace js