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"
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
;
27 class GenericPrinterPrintfTarget
: public mozilla::PrintfTarget
{
29 explicit GenericPrinterPrintfTarget(js::GenericPrinter
& p
) : printer(p
) {}
31 bool append(const char* sp
, size_t len
) override
{
37 js::GenericPrinter
& printer
;
44 void GenericPrinter::reportOutOfMemory() {
51 void GenericPrinter::put(mozilla::Span
<const JS::Latin1Char
> str
) {
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
) {
64 void GenericPrinter::putString(JSContext
* cx
, JSString
* str
) {
65 StringSegmentRange
iter(cx
);
66 if (!iter
.init(str
)) {
70 JS::AutoCheckCannotGC nogc
;
71 while (!iter
.empty()) {
72 JSLinearString
* linear
= iter
.front();
73 if (linear
->hasLatin1Chars()) {
74 put(linear
->latin1Range(nogc
));
76 put(linear
->twoByteRange(nogc
));
78 if (!iter
.popFront()) {
85 void GenericPrinter::printf(const char* fmt
, ...) {
92 void GenericPrinter::vprintf(const char* fmt
, va_list ap
) {
93 // Simple shortcut to avoid allocating strings.
94 if (strchr(fmt
, '%') == nullptr) {
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
);
110 char* newBuf
= (char*)js_arena_realloc(arena
, base
, newSize
);
117 base
[size
- 1] = '\0';
121 StringPrinter::StringPrinter(arena_id_t arena
, JSContext
* maybeCx
,
122 bool shouldReportOOM
)
127 shouldReportOOM(maybeCx
&& shouldReportOOM
),
134 StringPrinter::~StringPrinter() {
143 bool StringPrinter::init() {
144 MOZ_ASSERT(!initialized
);
145 base
= js_pod_arena_malloc
<char>(arena
, DefaultSize
);
148 forwardOutOfMemory();
156 base
[size
- 1] = '\0';
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() {
168 forwardOutOfMemory();
179 return UniqueChars(str
);
182 JSString
* StringPrinter::releaseJS(JSContext
* cx
) {
184 MOZ_ASSERT_IF(maybeCx
, maybeCx
== cx
);
185 forwardOutOfMemory();
191 // Extract StringPrinter data.
192 size_t len
= length();
193 UniqueChars
str(base
);
195 // Reset StringPrinter.
203 // Convert extracted data to a JSString, reusing the current buffer whenever
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
);
213 if (encoding
== JS::SmallestEncoding::Latin1
) {
214 UniqueLatin1Chars
latin1(
215 UTF8CharsToNewLatin1CharsZ(cx
, utf8
, &length
, js::StringBufferArena
)
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
)
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)) {
245 char* sb
= base
+ offset
;
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
);
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
;
268 js_memcpy(bp
, s
, len
);
274 void StringPrinter::putString(JSContext
* cx
, JSString
* s
) {
276 InvariantChecker
ic(this);
278 JSLinearString
* linear
= s
->ensureLinear(cx
);
283 size_t length
= JS::GetDeflatedUTF8StringLength(linear
);
285 char* buffer
= reserve(length
);
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() {
301 if (maybeCx
&& shouldReportOOM
) {
302 ReportOutOfMemory(maybeCx
);
306 const char js_EscapeMap
[] = {
321 static const char JSONEscapeMap
[] = {
334 template <QuoteTarget target
, typename CharT
>
335 JS_PUBLIC_API
void QuoteString(Sprinter
* sp
,
336 const mozilla::Range
<const CharT
>& chars
,
338 MOZ_ASSERT_IF(target
== QuoteTarget::JSON
, quote
== '\0');
343 if (target
== QuoteTarget::String
) {
344 StringEscape
esc(quote
);
345 EscapePrinter
ep(*sp
, esc
);
348 MOZ_ASSERT(target
== QuoteTarget::JSON
);
350 EscapePrinter
ep(*sp
, esc
);
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
);
376 StringEscape
esc(quote
);
377 EscapePrinter
ep(*sp
, esc
);
378 ep
.putString(sp
->maybeCx
, str
);
384 JS_PUBLIC_API UniqueChars
QuoteString(JSContext
* cx
, JSString
* str
,
385 char quote
/* = '\0' */) {
386 Sprinter
sprinter(cx
);
387 if (!sprinter
.init()) {
390 QuoteString(&sprinter
, str
, quote
);
391 return sprinter
.release();
394 JS_PUBLIC_API
void JSONQuoteString(StringPrinter
* sp
, JSString
* str
) {
395 MOZ_ASSERT(sp
->maybeCx
);
397 EscapePrinter
ep(*sp
, esc
);
398 ep
.putString(sp
->maybeCx
, str
);
401 Fprinter::Fprinter(FILE* fp
) : file_(nullptr), init_(false) { init(fp
); }
404 Fprinter::~Fprinter() { MOZ_ASSERT_IF(init_
, !file_
); }
407 bool Fprinter::init(const char* path
) {
409 file_
= fopen(path
, "w");
417 void Fprinter::init(FILE* fp
) {
423 void Fprinter::flush() {
428 void Fprinter::finish() {
436 void Fprinter::put(const char* s
, size_t len
) {
437 if (hadOutOfMemory()) {
442 int i
= fwrite(s
, /*size=*/1, /*nitems=*/len
, file_
);
443 if (size_t(i
) != len
) {
448 if ((file_
== stderr
) && (IsDebuggerPresent())) {
449 UniqueChars buf
= DuplicateString(s
, len
);
454 OutputDebugStringA(buf
.get());
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 {
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() {
485 void LSprinter::put(const char* s
, size_t len
) {
486 if (hadOutOfMemory()) {
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;
503 AlignBytes(sizeof(Chunk
) + overflow
, js::detail::LIFO_ALLOC_ALIGN
);
505 LifoAlloc::AutoFallibleScope
fallibleAllocator(alloc_
);
506 last
= reinterpret_cast<Chunk
*>(alloc_
->alloc(allocLength
));
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
;
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
;
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
;
546 PodCopy(tail_
->end() - unused_
, s
, overflow
);
548 MOZ_ASSERT(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]);
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]);
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
) {
603 if (pendingIndent_
) {
605 pendingIndent_
= false;
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
);