Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / js / public / Printer.h
blob644ffa9176cef1e077b359894779fa3fec427a50
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 #ifndef js_Printer_h
8 #define js_Printer_h
10 #include "mozilla/Attributes.h"
11 #include "mozilla/glue/Debug.h"
12 #include "mozilla/Range.h"
14 #include <stdarg.h>
15 #include <stddef.h>
16 #include <stdio.h>
17 #include <string.h>
19 #include "js/TypeDecls.h"
20 #include "js/Utility.h"
22 // [SMDOC] *Printer, Sprinter, Fprinter, ...
24 // # Motivation
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
31 // file.
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.
37 // # Design
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);
48 // }
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`.
67 // # Implementations
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
77 // `releaseJS`.
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
89 // necessary.
91 // # Print UTF-16
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
113 // argument.
115 // To use an escape policy, the printer should be wrapped using an EscapePrinter
116 // as follows:
118 // {
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.
127 // ep.put(chars);
128 // }
131 namespace js {
133 class LifoAlloc;
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 {
141 protected:
142 bool hadOOM_; // whether reportOutOfMemory() has been called.
144 constexpr GenericPrinter() : hadOOM_(false) {}
146 public:
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
156 // in the output.
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
190 // before.
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
201 // ever returned.
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 */
208 // Report that a string operation failed to get the memory it requested.
209 virtual void reportOutOfMemory();
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
216 // buffering.
217 class JS_PUBLIC_API StringPrinter : public GenericPrinter {
218 public:
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(); }
230 JSContext* maybeCx;
232 private:
233 static const size_t DefaultSize;
234 #ifdef DEBUG
235 bool initialized; // true if this is initialized, use for debug builds
236 #endif
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.
247 arena_id_t arena;
249 private:
250 [[nodiscard]] bool realloc_(size_t newSize);
252 protected:
253 // JSContext* parameter is optional and can be omitted if the following
254 // are not used.
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);
263 ~StringPrinter();
265 JS::UniqueChars releaseChars();
266 JSString* releaseJS(JSContext* cx);
268 public:
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
277 // success.
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
282 // printing.
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 {
307 public:
308 explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true)
309 : StringPrinter(js::MallocArena, maybeCx, shouldReportOOM) {}
310 ~Sprinter() {}
312 JS::UniqueChars release() { return releaseChars(); }
315 class JS_PUBLIC_API JSSprinter : public StringPrinter {
316 public:
317 explicit JSSprinter(JSContext* cx)
318 : StringPrinter(js::StringBufferArena, cx, true) {}
319 ~JSSprinter() {}
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 {
326 private:
327 FILE* file_;
328 bool init_;
330 public:
331 explicit Fprinter(FILE* fp);
333 constexpr Fprinter() : file_(nullptr), init_(false) {}
335 #ifdef DEBUG
336 ~Fprinter();
337 #endif
339 // Initialize this printer, returns false on error.
340 [[nodiscard]] bool init(const char* path);
341 void init(FILE* fp);
342 bool isInitialized() const { return file_ != nullptr; }
343 void flush() override;
344 void finish();
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,
348 // not just OOM.)
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 {
356 public:
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 {
370 private:
371 struct Chunk {
372 Chunk* next;
373 size_t length;
375 char* chars() { return reinterpret_cast<char*>(this + 1); }
376 char* end() { return chars() + length; }
379 private:
380 LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations.
381 Chunk* head_;
382 Chunk* tail_;
383 size_t unused_;
385 public:
386 explicit LSprinter(LifoAlloc* lifoAlloc);
387 ~LSprinter();
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.
394 void clear();
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]))) {
409 return i;
412 return len;
415 private:
416 Delegate& out;
417 Escape& esc;
419 public:
420 EscapePrinter(Delegate& out, Escape& esc) : out(out), esc(esc) {}
421 ~EscapePrinter() {}
423 using GenericPrinter::put;
424 void put(const char* s, size_t len) override {
425 const char* b = s;
426 while (len) {
427 size_t index = lengthOfSafeChars(b, len);
428 if (index) {
429 out.put(b, index);
430 len -= index;
431 b += index;
433 if (len) {
434 esc.convertInto(out, char16_t(uint8_t(*b)));
435 len -= 1;
436 b += 1;
441 inline void putChar(const char c) override {
442 if (esc.isSafeChar(char16_t(uint8_t(c)))) {
443 out.putChar(char(c));
444 return;
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));
452 return;
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));
460 return;
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 reportOutOfMemory() final { out.reportOutOfMemory(); }
473 bool hadOutOfMemory() const final { return out.hadOutOfMemory(); }
476 class JS_PUBLIC_API JSONEscape {
477 public:
478 bool isSafeChar(char16_t c);
479 void convertInto(GenericPrinter& out, char16_t c);
482 class JS_PUBLIC_API StringEscape {
483 private:
484 const char quote = '\0';
486 public:
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.
502 bool pendingIndent_;
504 // Put an indent to `out_`
505 void putIndent();
506 // Put `s` to `out_`, inserting an indent if we need to
507 void putWithMaybeIndent(const char* s, size_t len);
509 public:
510 explicit IndentedPrinter(GenericPrinter& out, uint32_t indentLevel = 0,
511 uint32_t indentAmount = 2)
512 : out_(out),
513 indentLevel_(indentLevel),
514 indentAmount_(indentAmount),
515 pendingIndent_(false) {}
517 // Automatically insert and remove and indent for a scope
518 class AutoIndent {
519 IndentedPrinter& printer_;
521 public:
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,
544 char quote = '\0');
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,
549 char quote = '\0');
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,
561 char quote = '\0');
563 } // namespace js
565 #endif // js_Printer_h