Printer.h (22960B)
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 #ifndef js_Printer_h 8 #define js_Printer_h 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/glue/Debug.h" 12 #include "mozilla/Range.h" 13 #include "mozilla/Vector.h" 14 15 #include <stdarg.h> 16 #include <stddef.h> 17 #include <stdio.h> 18 #include <string.h> 19 20 #include "js/TypeDecls.h" 21 #include "js/Utility.h" 22 23 // [SMDOC] *Printer, Sprinter, Fprinter, ... 24 // 25 // # Motivation 26 // 27 // In many places, we want to have functions which are capable of logging 28 // various data structures. Previously, we had logging functions for each 29 // storage, such as using `fwrite`, `printf` or `snprintf`. In additional cases, 30 // many of these logging options were using a string serializing logging 31 // function, only to discard the allocated string after it had been copied to a 32 // file. 33 // 34 // GenericPrinter is an answer to avoid excessive amount of temporary 35 // allocations which are used once, and a way to make logging functions work 36 // independently of the backend they are used with. 37 // 38 // # Design 39 // 40 // The GenericPrinter implements most of `put`, `printf`, `vprintf` and 41 // `putChar` functions, which are implemented using `put` and `putChar` 42 // functions in the derivative classes. Thus, one does not have to reimplement 43 // `putString` nor `printf` for each printer. 44 // 45 // // Logging the value N to whatever printer is provided such as 46 // // a file or a string. 47 // void logN(GenericPrinter& out) { 48 // out.printf("[Logging] %d\n", this->n); 49 // } 50 // 51 // The printing functions are infallible, from the logging functions 52 // perspective. If an issue happens while printing, this would be recorded by 53 // the Printer, and this can be tested using `hadOutOfMemory` function by the 54 // owner of the Printer instance. 55 // 56 // Even in case of failure, printing functions should remain safe to use. Thus 57 // calling `put` twice in a row is safe even if no check for `hadOutOfMemory` is 58 // performed. This is necessary to simplify the control flow and avoid bubble up 59 // failures out of logging functions. 60 // 61 // Note, being safe to use does not imply correctness. In case of failure the 62 // correctness of the printed characters is no longer guarantee. One should use 63 // `hadOutOfMemory` function to know if any failure happened which might have 64 // caused incorrect content to be saved. In some cases, such as `Sprinter`, 65 // where the string buffer can be extracted, the returned value would account 66 // for checking `hadOutOfMemory`. 67 // 68 // # Implementations 69 // 70 // The GenericPrinter is a base class where the derivative classes are providing 71 // different implementations which have their own advantages and disadvantages: 72 // 73 // - Fprinter: FILE* printer. Write the content directly to a file. 74 // 75 // - Sprinter: System allocator C-string buffer. Write the content to a buffer 76 // which is reallocated as more content is added. The buffer can then be 77 // extracted into a C-string or a JSString, respectively using `release` and 78 // `releaseJS`. 79 // 80 // - LSprinter: LifoAlloc C-string rope. Write the content to a list of chunks 81 // in a LifoAlloc buffer, no-reallocation occur but one should use 82 // `exportInto` to serialize its content to a Sprinter or a Fprinter. This is 83 // useful to avoid reallocation copies, while using an existing LifoAlloc. 84 // 85 // - SEPrinter: Roughly the same as Fprinter for stderr, except it goes through 86 // printf_stderr, which makes sure the output goes to a useful place: the 87 // Android log or the Windows debug output. 88 // 89 // - EscapePrinter: Wrapper around other printers, to escape characters when 90 // necessary. 91 // 92 // # Print UTF-16 93 // 94 // The GenericPrinter only handle `char` inputs, which is good enough for ASCII 95 // and Latin1 character sets. However, to handle UTF-16, one should use an 96 // EscapePrinter as well as a policy for escaping characters. 97 // 98 // One might require different escaping policies based on the escape sequences 99 // and based on the set of accepted character for the content generated. For 100 // example, JSON does not specify \x<XX> escape sequences. 101 // 102 // Today the following escape policies exists: 103 // 104 // - StringEscape: Produce C-like escape sequences: \<c>, \x<XX> and \u<XXXX>. 105 // - JSONEscape: Produce JSON escape sequences: \<c> and \u<XXXX>. 106 // 107 // An escape policy is defined by 2 functions: 108 // 109 // bool isSafeChar(char16_t c): 110 // Returns whether a character can be printed without being escaped. 111 // 112 // void convertInto(GenericPrinter& out, char16_t c): 113 // Calls the printer with the escape sequence for the character given as 114 // argument. 115 // 116 // To use an escape policy, the printer should be wrapped using an EscapePrinter 117 // as follows: 118 // 119 // { 120 // // The escaped string is surrounded by double-quotes, escape the double 121 // // quotes as well. 122 // StringEscape esc('"'); 123 // 124 // // Wrap our existing `GenericPrinter& out` using the `EscapePrinter`. 125 // EscapePrinter ep(out, esc); 126 // 127 // // Append a sequence of characters which might contain UTF-16 characters. 128 // ep.put(chars); 129 // } 130 // 131 132 namespace js { 133 134 class LifoAlloc; 135 136 // Generic printf interface, similar to an ostream in the standard library. 137 // 138 // This class is useful to make generic printers which can work either with a 139 // file backend, with a buffer allocated with an JSContext or a link-list 140 // of chunks allocated with a LifoAlloc. 141 class JS_PUBLIC_API GenericPrinter { 142 protected: 143 bool hadOOM_; // whether setPendingOutOfMemory() has been called. 144 145 constexpr GenericPrinter() : hadOOM_(false) {} 146 147 public: 148 // Puts |len| characters from |s| at the current position. This function might 149 // silently fail and the error can be tested using `hadOutOfMemory()`. Calling 150 // this function or any other printing functions after a failures is accepted, 151 // but the outcome would still remain incorrect and `hadOutOfMemory()` would 152 // still report any of the previous errors. 153 virtual void put(const char* s, size_t len) = 0; 154 inline void put(const char* s) { put(s, strlen(s)); } 155 inline void put(mozilla::Span<const char> s) { put(s.data(), s.size()); }; 156 157 // Put a mozilla::Span / mozilla::Range of Latin1Char or char16_t characters 158 // in the output. 159 // 160 // Note that the char16_t variant is expected to crash unless putChar is 161 // overriden to handle properly the full set of WTF-16 character set. 162 virtual void put(mozilla::Span<const JS::Latin1Char> str); 163 virtual void put(mozilla::Span<const char16_t> str); 164 165 // Same as the various put function but only appending a single character. 166 // 167 // Note that the char16_t variant is expected to crash unless putChar is 168 // overriden to handle properly the full set of WTF-16 character set. 169 virtual inline void putChar(const char c) { put(&c, 1); } 170 virtual inline void putChar(const JS::Latin1Char c) { putChar(char(c)); } 171 virtual inline void putChar(const char16_t c) { 172 MOZ_CRASH("Use an EscapePrinter to handle all characters"); 173 } 174 175 virtual void putString(JSContext* cx, JSString* str); 176 177 // Prints a formatted string into the buffer. 178 void printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); 179 void vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0); 180 181 // In some cases, such as handling JSRopes in a less-quadratic worse-case, 182 // it might be useful to copy content which has already been generated. 183 // 184 // If the buffer is back-readable, then this function should return `true` 185 // and `putFromIndex` should be implemented to delegate to a `put` call at 186 // the matching index and the corresponding length. To provide the index 187 // argument of `putFromIndex`, the `index` method should also be implemented 188 // to return the index within the inner buffer used by the printer. 189 virtual bool canPutFromIndex() const { return false; } 190 191 // Append to the current buffer, bytes which have previously been appended 192 // before. 193 virtual void putFromIndex(size_t index, size_t length) { 194 MOZ_CRASH("Calls to putFromIndex should be guarded by canPutFromIndex."); 195 } 196 197 // When the printer has a seekable buffer and `canPutFromIndex` returns 198 // `true`, this function can return the `index` of the next character to be 199 // added to the buffer. 200 // 201 // This function is monotonic. Thus, if the printer encounter an 202 // Out-Of-Memory issue, then the returned index should be the maximal value 203 // ever returned. 204 virtual size_t index() const { return 0; } 205 206 // In some printers, this ensure that the content is fully written. 207 virtual void flush() { /* Do nothing */ } 208 209 // Set a flag that a string operation failed to get the memory it requested. 210 // The pending out of memory error should be handled by the consumer. 211 virtual void setPendingOutOfMemory(); 212 213 // Return true if this Sprinter ran out of memory. 214 virtual bool hadOutOfMemory() const { return hadOOM_; } 215 }; 216 217 // Sprintf / JSSprintf, but with unlimited and automatically allocated 218 // buffering. 219 class JS_PUBLIC_API StringPrinter : public GenericPrinter { 220 public: 221 // Check that the invariant holds at the entry and exit of a scope. 222 struct InvariantChecker { 223 const StringPrinter* parent; 224 225 explicit InvariantChecker(const StringPrinter* p) : parent(p) { 226 parent->checkInvariants(); 227 } 228 229 ~InvariantChecker() { parent->checkInvariants(); } 230 }; 231 232 JSContext* maybeCx; 233 234 private: 235 static const size_t DefaultSize; 236 #ifdef DEBUG 237 bool initialized; // true if this is initialized, use for debug builds 238 #endif 239 bool shouldReportOOM; // whether to report OOM to the maybeCx 240 char* base; // malloc'd buffer address 241 size_t size; // size of buffer allocated at base 242 ptrdiff_t offset; // offset of next free char in buffer 243 244 // The arena to be used by jemalloc to allocate the string into. This is 245 // selected by the child classes when calling the constructor. JSStrings have 246 // a different arena than strings which do not belong to the JS engine, and as 247 // such when building a JSString with the intent of avoiding reallocation, the 248 // destination arena has to be selected upfront. 249 arena_id_t arena; 250 251 private: 252 [[nodiscard]] bool realloc_(size_t newSize); 253 254 protected: 255 // JSContext* parameter is optional and can be omitted if the following 256 // are not used. 257 // * putString method with JSString 258 // * QuoteString function with JSString 259 // * JSONQuoteString function with JSString 260 // 261 // If JSContext* parameter is not provided, or shouldReportOOM is false, 262 // the consumer should manually report OOM on any failure. 263 explicit StringPrinter(arena_id_t arena, JSContext* maybeCx = nullptr, 264 bool shouldReportOOM = true); 265 ~StringPrinter(); 266 267 JS::UniqueChars releaseChars(); 268 JSString* releaseJS(JSContext* cx); 269 270 public: 271 // Initialize this sprinter, returns false on error. 272 [[nodiscard]] bool init(); 273 274 void checkInvariants() const; 275 276 // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the 277 // attempt succeeds, return a pointer to the start of that space and adjust 278 // the internal content. The caller *must* completely fill this space on 279 // success. 280 char* reserve(size_t len); 281 282 // Puts |len| characters from |s| at the current position. May OOM, which must 283 // be checked by testing the return value of releaseJS() at the end of 284 // printing. 285 virtual void put(const char* s, size_t len) final; 286 using GenericPrinter::put; // pick up |put(const char* s);| 287 288 virtual bool canPutFromIndex() const final { return true; } 289 virtual void putFromIndex(size_t index, size_t length) final { 290 MOZ_ASSERT(index <= this->index()); 291 MOZ_ASSERT(index + length <= this->index()); 292 put(base + index, length); 293 } 294 virtual size_t index() const final { return length(); } 295 296 virtual void putString(JSContext* cx, JSString* str) final; 297 298 size_t length() const; 299 300 // When an OOM has already been reported on the Sprinter, this function will 301 // forward this error to the JSContext given in the Sprinter initialization. 302 // 303 // If no JSContext had been provided or the Sprinter is configured to not 304 // report OOM, then nothing happens. 305 void forwardOutOfMemory(); 306 }; 307 308 class JS_PUBLIC_API Sprinter : public StringPrinter { 309 public: 310 explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true) 311 : StringPrinter(js::MallocArena, maybeCx, shouldReportOOM) {} 312 ~Sprinter() {} 313 314 JS::UniqueChars release() { return releaseChars(); } 315 }; 316 317 class JS_PUBLIC_API JSSprinter : public StringPrinter { 318 public: 319 explicit JSSprinter(JSContext* cx) 320 : StringPrinter(js::StringBufferArena, cx, true) {} 321 ~JSSprinter() {} 322 323 JSString* release(JSContext* cx) { return releaseJS(cx); } 324 }; 325 326 // FixedBufferPrinter, print to a fixed-size buffer. The string in the buffer 327 // will always be null-terminated after being passed to the constructor. 328 class FixedBufferPrinter final : public GenericPrinter { 329 private: 330 // The first char in the buffer where put will append the next string 331 char* buffer_; 332 // The remaining size available in the buffer 333 size_t size_; 334 335 public: 336 constexpr FixedBufferPrinter(char* buf, size_t size) 337 : buffer_(buf), size_(size) { 338 MOZ_ASSERT(buffer_); 339 memset(buffer_, 0, size_); 340 } 341 342 // Puts |len| characters from |s| at the current position. 343 // If the buffer fills up, this won't do anything. 344 void put(const char* s, size_t len) override; 345 using GenericPrinter::put; // pick up |put(const char* s);| 346 }; 347 348 // Fprinter, print a string directly into a file. 349 class JS_PUBLIC_API Fprinter final : public GenericPrinter { 350 private: 351 FILE* file_; 352 bool init_; 353 354 public: 355 explicit Fprinter(FILE* fp); 356 357 constexpr Fprinter() : file_(nullptr), init_(false) {} 358 359 #ifdef DEBUG 360 ~Fprinter(); 361 #endif 362 363 // Initialize this printer, returns false on error. 364 [[nodiscard]] bool init(const char* path); 365 void init(FILE* fp); 366 bool isInitialized() const { return file_ != nullptr; } 367 void flush() override; 368 void finish(); 369 370 // Puts |len| characters from |s| at the current position. Errors may be 371 // detected with hadOutOfMemory() (which will be set for any fwrite() error, 372 // not just OOM.) 373 void put(const char* s, size_t len) override; 374 using GenericPrinter::put; // pick up |put(const char* s);| 375 }; 376 377 // SEprinter, print using printf_stderr (goes to Android log, Windows debug, 378 // else just stderr). 379 class SEprinter final : public GenericPrinter { 380 public: 381 constexpr SEprinter() {} 382 383 // Puts |len| characters from |s| at the current position. Ignores errors. 384 virtual void put(const char* s, size_t len) override { 385 printf_stderr("%.*s", int(len), s); 386 } 387 using GenericPrinter::put; // pick up |put(const char* s);| 388 }; 389 390 // LSprinter, is similar to Sprinter except that instead of using an 391 // JSContext to allocate strings, it use a LifoAlloc as a backend for the 392 // allocation of the chunk of the string. 393 class JS_PUBLIC_API LSprinter final : public GenericPrinter { 394 private: 395 struct Chunk { 396 Chunk* next; 397 size_t length; 398 399 char* chars() { return reinterpret_cast<char*>(this + 1); } 400 char* end() { return chars() + length; } 401 }; 402 403 private: 404 LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations. 405 Chunk* head_; 406 Chunk* tail_; 407 size_t unused_; 408 409 public: 410 explicit LSprinter(LifoAlloc* lifoAlloc); 411 ~LSprinter(); 412 413 // Copy the content of the chunks into another printer, such that we can 414 // flush the content of this printer to a file. 415 void exportInto(GenericPrinter& out) const; 416 417 // Drop the current string, and let them be free with the LifoAlloc. 418 void clear(); 419 420 // Puts |len| characters from |s| at the current position. 421 virtual void put(const char* s, size_t len) override; 422 using GenericPrinter::put; // pick up |put(const char* s);| 423 }; 424 425 // Escaping printers work like any other printer except that any added character 426 // are checked for escaping sequences. This one would escape a string such that 427 // it can safely be embedded in a JS string. 428 template <typename Delegate, typename Escape> 429 class JS_PUBLIC_API EscapePrinter final : public GenericPrinter { 430 size_t lengthOfSafeChars(const char* s, size_t len) { 431 for (size_t i = 0; i < len; i++) { 432 if (!esc.isSafeChar(uint8_t(s[i]))) { 433 return i; 434 } 435 } 436 return len; 437 } 438 439 private: 440 Delegate& out; 441 Escape& esc; 442 443 public: 444 EscapePrinter(Delegate& out, Escape& esc) : out(out), esc(esc) {} 445 ~EscapePrinter() {} 446 447 using GenericPrinter::put; 448 void put(const char* s, size_t len) override { 449 const char* b = s; 450 while (len) { 451 size_t index = lengthOfSafeChars(b, len); 452 if (index) { 453 out.put(b, index); 454 len -= index; 455 b += index; 456 } 457 if (len) { 458 esc.convertInto(out, char16_t(uint8_t(*b))); 459 len -= 1; 460 b += 1; 461 } 462 } 463 } 464 465 inline void putChar(const char c) override { 466 if (esc.isSafeChar(char16_t(uint8_t(c)))) { 467 out.putChar(char(c)); 468 return; 469 } 470 esc.convertInto(out, char16_t(uint8_t(c))); 471 } 472 473 inline void putChar(const JS::Latin1Char c) override { 474 if (esc.isSafeChar(char16_t(c))) { 475 out.putChar(char(c)); 476 return; 477 } 478 esc.convertInto(out, char16_t(c)); 479 } 480 481 inline void putChar(const char16_t c) override { 482 if (esc.isSafeChar(c)) { 483 out.putChar(char(c)); 484 return; 485 } 486 esc.convertInto(out, c); 487 } 488 489 // Forward calls to delegated printer. 490 bool canPutFromIndex() const override { return out.canPutFromIndex(); } 491 void putFromIndex(size_t index, size_t length) final { 492 out.putFromIndex(index, length); 493 } 494 size_t index() const final { return out.index(); } 495 void flush() final { out.flush(); } 496 void setPendingOutOfMemory() final { out.setPendingOutOfMemory(); } 497 bool hadOutOfMemory() const final { return out.hadOutOfMemory(); } 498 }; 499 500 class JS_PUBLIC_API JSONEscape { 501 public: 502 bool isSafeChar(char16_t c); 503 void convertInto(GenericPrinter& out, char16_t c); 504 }; 505 506 class JS_PUBLIC_API StringEscape { 507 private: 508 const char quote = '\0'; 509 510 public: 511 explicit StringEscape(const char quote = '\0') : quote(quote) {} 512 513 bool isSafeChar(char16_t c); 514 void convertInto(GenericPrinter& out, char16_t c); 515 }; 516 517 class JS_PUBLIC_API WATStringEscape { 518 public: 519 bool isSafeChar(char16_t c); 520 void convertInto(GenericPrinter& out, char16_t c); 521 }; 522 523 // A GenericPrinter that can format its output in a structured way, with nice 524 // formatting. 525 // 526 // Suppose you want to print wasm structs, and you want to change the 527 // presentation depending on the number of fields: 528 // 529 // (struct) 530 // (struct (field i32)) 531 // (struct 532 // (field i32) 533 // (field i64) 534 // ) 535 // 536 // All three of these can be handled identically with quite straightforward 537 // code: 538 // 539 // out.printf("(struct"); 540 // { 541 // StructuredPrinter::Scope _(out); 542 // 543 // for (auto field : fields) { 544 // out.brk(" ", "\n"); 545 // out.printf("(field "); 546 // DumpFieldType(field.type, out); 547 // out.printf(")"); 548 // } 549 // out.brk("", "\n"); 550 // 551 // if (fields.length() > 1) { 552 // out.expand(); 553 // } 554 // } 555 // out.printf(")"); 556 // 557 // The `brk` method can be used to emit one of two "break" characters depending 558 // on whether the output is "expanded" or "collapsed". The decision about which 559 // style to emit is made later, by conditionally calling `out.expand()`. 560 // Additionally, the use of `StructuredPrinter::Scope` ensures that the struct 561 // fields are indented *if* the output is expanded. 562 // 563 // Newlines may still be printed at any time. Newlines will force the current 564 // scope to be expanded, along with any parent scopes. 565 class JS_PUBLIC_API StructuredPrinter final : public GenericPrinter { 566 GenericPrinter& out_; 567 568 // The number of spaces to insert for each indent. 569 int indentAmount_; 570 bool pendingIndent_; 571 572 // The index of the last expanded scope (or -1 if all scopes are collapsed). 573 int expandedDepth_ = -1; 574 575 struct Break { 576 uint32_t bufferPos; 577 bool isCollapsed; 578 const char* collapsed; 579 const char* expanded; 580 }; 581 582 struct ScopeInfo { 583 uint32_t startPos; 584 int indent; 585 }; 586 587 // Content is buffered while in collapsed mode in case it gets expanded later. 588 mozilla::Vector<char, 80> buffer_; 589 // Info about break characters in the buffer. 590 // Cleared when the buffer is cleared. 591 mozilla::Vector<Break, 8> breaks_; 592 // The stack of scopes maintained by the printer. 593 mozilla::Vector<ScopeInfo, 16> scopes_; 594 595 int scopeDepth() { return int(scopes_.length()) - 1; } 596 597 void putIndent(int level = -1); 598 void putBreak(const Break& brk); 599 void putWithMaybeIndent(const char* s, size_t len, int level = -1); 600 601 public: 602 explicit StructuredPrinter(GenericPrinter& out, int indentAmount = 2) 603 : out_(out), indentAmount_(indentAmount) { 604 pushScope(); 605 } 606 ~StructuredPrinter() { 607 popScope(); 608 flush(); 609 } 610 611 void pushScope(); 612 void popScope(); 613 614 void brk(const char* collapsed, const char* expanded); 615 void expand(); 616 bool isExpanded(); 617 618 void flush() override; 619 620 class Scope { 621 StructuredPrinter& printer_; 622 623 public: 624 explicit Scope(StructuredPrinter& printer) : printer_(printer) { 625 printer_.pushScope(); 626 } 627 ~Scope() { printer_.popScope(); } 628 }; 629 630 virtual void put(const char* s, size_t len) override; 631 using GenericPrinter::put; // pick up |inline void put(const char* s);| 632 }; 633 634 // Map escaped code to the letter/symbol escaped with a backslash. 635 extern const char js_EscapeMap[]; 636 637 // Return a C-string containing the chars in str, with any non-printing chars 638 // escaped. If the optional quote parameter is present and is not '\0', quotes 639 // (as specified by the quote argument) are also escaped, and the quote 640 // character is appended at the beginning and end of the result string. 641 // The returned string is guaranteed to contain only ASCII characters. 642 extern JS_PUBLIC_API JS::UniqueChars QuoteString(JSContext* cx, JSString* str, 643 char quote = '\0'); 644 645 // Appends the quoted string to the given Sprinter. Follows the same semantics 646 // as QuoteString from above. 647 extern JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str, 648 char quote = '\0'); 649 650 // Appends the quoted string to the given Sprinter. Follows the same 651 // Appends the JSON quoted string to the given Sprinter. 652 extern JS_PUBLIC_API void JSONQuoteString(StringPrinter* sp, JSString* str); 653 654 // Internal implementation code for QuoteString methods above. 655 enum class QuoteTarget { String, JSON }; 656 657 template <QuoteTarget target, typename CharT> 658 void JS_PUBLIC_API QuoteString(Sprinter* sp, 659 const mozilla::Range<const CharT>& chars, 660 char quote = '\0'); 661 662 } // namespace js 663 664 #endif // js_Printer_h