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/. */
10 #include "mozilla/Attributes.h"
11 #include "mozilla/glue/Debug.h"
12 #include "mozilla/Range.h"
19 #include "js/TypeDecls.h"
20 #include "js/Utility.h"
22 // [SMDOC] *Printer, Sprinter, Fprinter, ...
26 // In many places, we want to have functions which are capable of logging
27 // various data structures. Previously, we had logging functions for each
28 // storage, such as using `fwrite`, `printf` or `snprintf`. In additional cases,
29 // many of these logging options were using a string serializing logging
30 // function, only to discard the allocated string after it had been copied to a
33 // GenericPrinter is an answer to avoid excessive amount of temporary
34 // allocations which are used once, and a way to make logging functions work
35 // independently of the backend they are used with.
39 // The GenericPrinter implements most of `put`, `printf`, `vprintf` and
40 // `putChar` functions, which are implemented using `put` and `putChar`
41 // functions in the derivative classes. Thus, one does not have to reimplement
42 // `putString` nor `printf` for each printer.
44 // // Logging the value N to whatever printer is provided such as
45 // // a file or a string.
46 // void logN(GenericPrinter& out) {
47 // out.printf("[Logging] %d\n", this->n);
50 // The printing functions are infallible, from the logging functions
51 // perspective. If an issue happens while printing, this would be recorded by
52 // the Printer, and this can be tested using `hadOutOfMemory` function by the
53 // owner of the Printer instance.
55 // Even in case of failure, printing functions should remain safe to use. Thus
56 // calling `put` twice in a row is safe even if no check for `hadOutOfMemory` is
57 // performed. This is necessary to simplify the control flow and avoid bubble up
58 // failures out of logging functions.
60 // Note, being safe to use does not imply correctness. In case of failure the
61 // correctness of the printed characters is no longer guarantee. One should use
62 // `hadOutOfMemory` function to know if any failure happened which might have
63 // caused incorrect content to be saved. In some cases, such as `Sprinter`,
64 // where the string buffer can be extracted, the returned value would account
65 // for checking `hadOutOfMemory`.
69 // The GenericPrinter is a base class where the derivative classes are providing
70 // different implementations which have their own advantages and disadvantages:
72 // - Fprinter: FILE* printer. Write the content directly to a file.
74 // - Sprinter: System allocator C-string buffer. Write the content to a buffer
75 // which is reallocated as more content is added. The buffer can then be
76 // extracted into a C-string or a JSString, respectively using `release` and
79 // - LSprinter: LifoAlloc C-string rope. Write the content to a list of chunks
80 // in a LifoAlloc buffer, no-reallocation occur but one should use
81 // `exportInto` to serialize its content to a Sprinter or a Fprinter. This is
82 // useful to avoid reallocation copies, while using an existing LifoAlloc.
84 // - SEPrinter: Roughly the same as Fprinter for stderr, except it goes through
85 // printf_stderr, which makes sure the output goes to a useful place: the
86 // Android log or the Windows debug output.
88 // - EscapePrinter: Wrapper around other printers, to escape characters when
93 // The GenericPrinter only handle `char` inputs, which is good enough for ASCII
94 // and Latin1 character sets. However, to handle UTF-16, one should use an
95 // EscapePrinter as well as a policy for escaping characters.
97 // One might require different escaping policies based on the escape sequences
98 // and based on the set of accepted character for the content generated. For
99 // example, JSON does not specify \x<XX> escape sequences.
101 // Today the following escape policies exists:
103 // - StringEscape: Produce C-like escape sequences: \<c>, \x<XX> and \u<XXXX>.
104 // - JSONEscape: Produce JSON escape sequences: \<c> and \u<XXXX>.
106 // An escape policy is defined by 2 functions:
108 // bool isSafeChar(char16_t c):
109 // Returns whether a character can be printed without being escaped.
111 // void convertInto(GenericPrinter& out, char16_t c):
112 // Calls the printer with the escape sequence for the character given as
115 // To use an escape policy, the printer should be wrapped using an EscapePrinter
119 // // The escaped string is surrounded by double-quotes, escape the double
120 // // quotes as well.
121 // StringEscape esc('"');
123 // // Wrap our existing `GenericPrinter& out` using the `EscapePrinter`.
124 // EscapePrinter ep(out, esc);
126 // // Append a sequence of characters which might contain UTF-16 characters.
135 // Generic printf interface, similar to an ostream in the standard library.
137 // This class is useful to make generic printers which can work either with a
138 // file backend, with a buffer allocated with an JSContext or a link-list
139 // of chunks allocated with a LifoAlloc.
140 class JS_PUBLIC_API GenericPrinter
{
142 bool hadOOM_
; // whether setPendingOutOfMemory() has been called.
144 constexpr GenericPrinter() : hadOOM_(false) {}
147 // Puts |len| characters from |s| at the current position. This function might
148 // silently fail and the error can be tested using `hadOutOfMemory()`. Calling
149 // this function or any other printing functions after a failures is accepted,
150 // but the outcome would still remain incorrect and `hadOutOfMemory()` would
151 // still report any of the previous errors.
152 virtual void put(const char* s
, size_t len
) = 0;
153 inline void put(const char* s
) { put(s
, strlen(s
)); }
155 // Put a mozilla::Span / mozilla::Range of Latin1Char or char16_t characters
158 // Note that the char16_t variant is expected to crash unless putChar is
159 // overriden to handle properly the full set of WTF-16 character set.
160 virtual void put(mozilla::Span
<const JS::Latin1Char
> str
);
161 virtual void put(mozilla::Span
<const char16_t
> str
);
163 // Same as the various put function but only appending a single character.
165 // Note that the char16_t variant is expected to crash unless putChar is
166 // overriden to handle properly the full set of WTF-16 character set.
167 virtual inline void putChar(const char c
) { put(&c
, 1); }
168 virtual inline void putChar(const JS::Latin1Char c
) { putChar(char(c
)); }
169 virtual inline void putChar(const char16_t c
) {
170 MOZ_CRASH("Use an EscapePrinter to handle all characters");
173 virtual void putString(JSContext
* cx
, JSString
* str
);
175 // Prints a formatted string into the buffer.
176 void printf(const char* fmt
, ...) MOZ_FORMAT_PRINTF(2, 3);
177 void vprintf(const char* fmt
, va_list ap
) MOZ_FORMAT_PRINTF(2, 0);
179 // In some cases, such as handling JSRopes in a less-quadratic worse-case,
180 // it might be useful to copy content which has already been generated.
182 // If the buffer is back-readable, then this function should return `true`
183 // and `putFromIndex` should be implemented to delegate to a `put` call at
184 // the matching index and the corresponding length. To provide the index
185 // argument of `putFromIndex`, the `index` method should also be implemented
186 // to return the index within the inner buffer used by the printer.
187 virtual bool canPutFromIndex() const { return false; }
189 // Append to the current buffer, bytes which have previously been appended
191 virtual void putFromIndex(size_t index
, size_t length
) {
192 MOZ_CRASH("Calls to putFromIndex should be guarded by canPutFromIndex.");
195 // When the printer has a seekable buffer and `canPutFromIndex` returns
196 // `true`, this function can return the `index` of the next character to be
197 // added to the buffer.
199 // This function is monotonic. Thus, if the printer encounter an
200 // Out-Of-Memory issue, then the returned index should be the maximal value
202 virtual size_t index() const { return 0; }
204 // In some printers, this ensure that the content is fully written.
205 virtual void flush() { /* Do nothing */ }
207 // Set a flag that a string operation failed to get the memory it requested.
208 // The pending out of memory error should be handled by the consumer.
209 virtual void setPendingOutOfMemory();
211 // Return true if this Sprinter ran out of memory.
212 virtual bool hadOutOfMemory() const { return hadOOM_
; }
215 // Sprintf / JSSprintf, but with unlimited and automatically allocated
217 class JS_PUBLIC_API StringPrinter
: public GenericPrinter
{
219 // Check that the invariant holds at the entry and exit of a scope.
220 struct InvariantChecker
{
221 const StringPrinter
* parent
;
223 explicit InvariantChecker(const StringPrinter
* p
) : parent(p
) {
224 parent
->checkInvariants();
227 ~InvariantChecker() { parent
->checkInvariants(); }
233 static const size_t DefaultSize
;
235 bool initialized
; // true if this is initialized, use for debug builds
237 bool shouldReportOOM
; // whether to report OOM to the maybeCx
238 char* base
; // malloc'd buffer address
239 size_t size
; // size of buffer allocated at base
240 ptrdiff_t offset
; // offset of next free char in buffer
242 // The arena to be used by jemalloc to allocate the string into. This is
243 // selected by the child classes when calling the constructor. JSStrings have
244 // a different arena than strings which do not belong to the JS engine, and as
245 // such when building a JSString with the intent of avoiding reallocation, the
246 // destination arena has to be selected upfront.
250 [[nodiscard
]] bool realloc_(size_t newSize
);
253 // JSContext* parameter is optional and can be omitted if the following
255 // * putString method with JSString
256 // * QuoteString function with JSString
257 // * JSONQuoteString function with JSString
259 // If JSContext* parameter is not provided, or shouldReportOOM is false,
260 // the consumer should manually report OOM on any failure.
261 explicit StringPrinter(arena_id_t arena
, JSContext
* maybeCx
= nullptr,
262 bool shouldReportOOM
= true);
265 JS::UniqueChars
releaseChars();
266 JSString
* releaseJS(JSContext
* cx
);
269 // Initialize this sprinter, returns false on error.
270 [[nodiscard
]] bool init();
272 void checkInvariants() const;
274 // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the
275 // attempt succeeds, return a pointer to the start of that space and adjust
276 // the internal content. The caller *must* completely fill this space on
278 char* reserve(size_t len
);
280 // Puts |len| characters from |s| at the current position. May OOM, which must
281 // be checked by testing the return value of releaseJS() at the end of
283 virtual void put(const char* s
, size_t len
) final
;
284 using GenericPrinter::put
; // pick up |put(const char* s);|
286 virtual bool canPutFromIndex() const final
{ return true; }
287 virtual void putFromIndex(size_t index
, size_t length
) final
{
288 MOZ_ASSERT(index
<= this->index());
289 MOZ_ASSERT(index
+ length
<= this->index());
290 put(base
+ index
, length
);
292 virtual size_t index() const final
{ return length(); }
294 virtual void putString(JSContext
* cx
, JSString
* str
) final
;
296 size_t length() const;
298 // When an OOM has already been reported on the Sprinter, this function will
299 // forward this error to the JSContext given in the Sprinter initialization.
301 // If no JSContext had been provided or the Sprinter is configured to not
302 // report OOM, then nothing happens.
303 void forwardOutOfMemory();
306 class JS_PUBLIC_API Sprinter
: public StringPrinter
{
308 explicit Sprinter(JSContext
* maybeCx
= nullptr, bool shouldReportOOM
= true)
309 : StringPrinter(js::MallocArena
, maybeCx
, shouldReportOOM
) {}
312 JS::UniqueChars
release() { return releaseChars(); }
315 class JS_PUBLIC_API JSSprinter
: public StringPrinter
{
317 explicit JSSprinter(JSContext
* cx
)
318 : StringPrinter(js::StringBufferArena
, cx
, true) {}
321 JSString
* release(JSContext
* cx
) { return releaseJS(cx
); }
324 // Fprinter, print a string directly into a file.
325 class JS_PUBLIC_API Fprinter final
: public GenericPrinter
{
331 explicit Fprinter(FILE* fp
);
333 constexpr Fprinter() : file_(nullptr), init_(false) {}
339 // Initialize this printer, returns false on error.
340 [[nodiscard
]] bool init(const char* path
);
342 bool isInitialized() const { return file_
!= nullptr; }
343 void flush() override
;
346 // Puts |len| characters from |s| at the current position. Errors may be
347 // detected with hadOutOfMemory() (which will be set for any fwrite() error,
349 void put(const char* s
, size_t len
) override
;
350 using GenericPrinter::put
; // pick up |put(const char* s);|
353 // SEprinter, print using printf_stderr (goes to Android log, Windows debug,
354 // else just stderr).
355 class SEprinter final
: public GenericPrinter
{
357 constexpr SEprinter() {}
359 // Puts |len| characters from |s| at the current position. Ignores errors.
360 virtual void put(const char* s
, size_t len
) override
{
361 printf_stderr("%.*s", int(len
), s
);
363 using GenericPrinter::put
; // pick up |put(const char* s);|
366 // LSprinter, is similar to Sprinter except that instead of using an
367 // JSContext to allocate strings, it use a LifoAlloc as a backend for the
368 // allocation of the chunk of the string.
369 class JS_PUBLIC_API LSprinter final
: public GenericPrinter
{
375 char* chars() { return reinterpret_cast<char*>(this + 1); }
376 char* end() { return chars() + length
; }
380 LifoAlloc
* alloc_
; // LifoAlloc used as a backend of chunk allocations.
386 explicit LSprinter(LifoAlloc
* lifoAlloc
);
389 // Copy the content of the chunks into another printer, such that we can
390 // flush the content of this printer to a file.
391 void exportInto(GenericPrinter
& out
) const;
393 // Drop the current string, and let them be free with the LifoAlloc.
396 // Puts |len| characters from |s| at the current position.
397 virtual void put(const char* s
, size_t len
) override
;
398 using GenericPrinter::put
; // pick up |put(const char* s);|
401 // Escaping printers work like any other printer except that any added character
402 // are checked for escaping sequences. This one would escape a string such that
403 // it can safely be embedded in a JS string.
404 template <typename Delegate
, typename Escape
>
405 class JS_PUBLIC_API EscapePrinter final
: public GenericPrinter
{
406 size_t lengthOfSafeChars(const char* s
, size_t len
) {
407 for (size_t i
= 0; i
< len
; i
++) {
408 if (!esc
.isSafeChar(uint8_t(s
[i
]))) {
420 EscapePrinter(Delegate
& out
, Escape
& esc
) : out(out
), esc(esc
) {}
423 using GenericPrinter::put
;
424 void put(const char* s
, size_t len
) override
{
427 size_t index
= lengthOfSafeChars(b
, len
);
434 esc
.convertInto(out
, char16_t(uint8_t(*b
)));
441 inline void putChar(const char c
) override
{
442 if (esc
.isSafeChar(char16_t(uint8_t(c
)))) {
443 out
.putChar(char(c
));
446 esc
.convertInto(out
, char16_t(uint8_t(c
)));
449 inline void putChar(const JS::Latin1Char c
) override
{
450 if (esc
.isSafeChar(char16_t(c
))) {
451 out
.putChar(char(c
));
454 esc
.convertInto(out
, char16_t(c
));
457 inline void putChar(const char16_t c
) override
{
458 if (esc
.isSafeChar(c
)) {
459 out
.putChar(char(c
));
462 esc
.convertInto(out
, c
);
465 // Forward calls to delegated printer.
466 bool canPutFromIndex() const override
{ return out
.canPutFromIndex(); }
467 void putFromIndex(size_t index
, size_t length
) final
{
468 out
.putFromIndex(index
, length
);
470 size_t index() const final
{ return out
.index(); }
471 void flush() final
{ out
.flush(); }
472 void setPendingOutOfMemory() final
{ out
.setPendingOutOfMemory(); }
473 bool hadOutOfMemory() const final
{ return out
.hadOutOfMemory(); }
476 class JS_PUBLIC_API JSONEscape
{
478 bool isSafeChar(char16_t c
);
479 void convertInto(GenericPrinter
& out
, char16_t c
);
482 class JS_PUBLIC_API StringEscape
{
484 const char quote
= '\0';
487 explicit StringEscape(const char quote
= '\0') : quote(quote
) {}
489 bool isSafeChar(char16_t c
);
490 void convertInto(GenericPrinter
& out
, char16_t c
);
493 // A GenericPrinter that formats everything at a nested indentation level.
494 class JS_PUBLIC_API IndentedPrinter final
: public GenericPrinter
{
495 GenericPrinter
& out_
;
496 // The number of indents to insert at the beginning of each line.
497 uint32_t indentLevel_
;
498 // The number of spaces to insert for each indent.
499 uint32_t indentAmount_
;
500 // Whether we have seen a line ending and should insert an indent at the
501 // next line fragment.
504 // Put an indent to `out_`
506 // Put `s` to `out_`, inserting an indent if we need to
507 void putWithMaybeIndent(const char* s
, size_t len
);
510 explicit IndentedPrinter(GenericPrinter
& out
, uint32_t indentLevel
= 0,
511 uint32_t indentAmount
= 2)
513 indentLevel_(indentLevel
),
514 indentAmount_(indentAmount
),
515 pendingIndent_(false) {}
517 // Automatically insert and remove and indent for a scope
519 IndentedPrinter
& printer_
;
522 explicit AutoIndent(IndentedPrinter
& printer
) : printer_(printer
) {
523 printer_
.setIndentLevel(printer_
.indentLevel() + 1);
525 ~AutoIndent() { printer_
.setIndentLevel(printer_
.indentLevel() - 1); }
528 uint32_t indentLevel() const { return indentLevel_
; }
529 void setIndentLevel(uint32_t indentLevel
) { indentLevel_
= indentLevel
; }
531 virtual void put(const char* s
, size_t len
) override
;
532 using GenericPrinter::put
; // pick up |inline void put(const char* s);|
535 // Map escaped code to the letter/symbol escaped with a backslash.
536 extern const char js_EscapeMap
[];
538 // Return a C-string containing the chars in str, with any non-printing chars
539 // escaped. If the optional quote parameter is present and is not '\0', quotes
540 // (as specified by the quote argument) are also escaped, and the quote
541 // character is appended at the beginning and end of the result string.
542 // The returned string is guaranteed to contain only ASCII characters.
543 extern JS_PUBLIC_API
JS::UniqueChars
QuoteString(JSContext
* cx
, JSString
* str
,
546 // Appends the quoted string to the given Sprinter. Follows the same semantics
547 // as QuoteString from above.
548 extern JS_PUBLIC_API
void QuoteString(Sprinter
* sp
, JSString
* str
,
551 // Appends the quoted string to the given Sprinter. Follows the same
552 // Appends the JSON quoted string to the given Sprinter.
553 extern JS_PUBLIC_API
void JSONQuoteString(StringPrinter
* sp
, JSString
* str
);
555 // Internal implementation code for QuoteString methods above.
556 enum class QuoteTarget
{ String
, JSON
};
558 template <QuoteTarget target
, typename CharT
>
559 void JS_PUBLIC_API
QuoteString(Sprinter
* sp
,
560 const mozilla::Range
<const CharT
>& chars
,
565 #endif // js_Printer_h