From 70df8513eb4c11061dd55e4bae1bdf5bea9a2b29 Mon Sep 17 00:00:00 2001 From: Cosmin Sabou Date: Fri, 15 Sep 2023 17:28:32 +0300 Subject: [PATCH] Backed out 22 changesets (bug 1839396) for causing build bustages on js/Printer.h. CLOSED TREE Backed out changeset 438a7580a804 (bug 1839396) Backed out changeset 0e43037688b7 (bug 1839396) Backed out changeset 05ffcc50ddb2 (bug 1839396) Backed out changeset 3db0e8cc147f (bug 1839396) Backed out changeset 92310695d5cf (bug 1839396) Backed out changeset 95880ae2420c (bug 1839396) Backed out changeset 8bd5a4df0d37 (bug 1839396) Backed out changeset 9cf77f46cf5e (bug 1839396) Backed out changeset 6ae1dde24527 (bug 1839396) Backed out changeset ffa0fc565bbf (bug 1839396) Backed out changeset ac5d44621417 (bug 1839396) Backed out changeset f1f71378d0a0 (bug 1839396) Backed out changeset ac986f1fee8a (bug 1839396) Backed out changeset f54b604c0c41 (bug 1839396) Backed out changeset 64a0729cfa91 (bug 1839396) Backed out changeset d06c9fc06b6e (bug 1839396) Backed out changeset 861cd467ad0d (bug 1839396) Backed out changeset 5e0270d189f5 (bug 1839396) Backed out changeset 00b288e75acf (bug 1839396) Backed out changeset 21e7991fe154 (bug 1839396) Backed out changeset 0f468ad1a4d1 (bug 1839396) Backed out changeset 91937beca944 (bug 1839396) --- build/win32/orderfile.txt | 1 + build/win64/orderfile.txt | 1 + js/public/Printer.h | 712 ++++++++++++------------------------ js/src/builtin/TestingFunctions.cpp | 27 +- js/src/frontend/ParserAtom.cpp | 4 +- js/src/gc/Nursery.cpp | 66 ++-- js/src/gc/Nursery.h | 2 +- js/src/gc/Statistics.cpp | 67 ++-- js/src/gc/Statistics.h | 2 +- js/src/jit/CodeGenerator.cpp | 4 +- js/src/jit/LIR.cpp | 6 +- js/src/jit/PerfSpewer.cpp | 46 +-- js/src/jit/RangeAnalysis.cpp | 6 +- js/src/shell/js.cpp | 45 +-- js/src/util/DumpFunctions.cpp | 80 ++-- js/src/util/Text.cpp | 4 +- js/src/vm/BytecodeUtil.cpp | 358 ++++++++++++------ js/src/vm/EnvironmentObject.cpp | 12 +- js/src/vm/JSScript.cpp | 181 ++++++--- js/src/vm/Printer.cpp | 339 ++++++++--------- js/src/vm/Scope.cpp | 65 +++- 21 files changed, 1022 insertions(+), 1006 deletions(-) rewrite js/public/Printer.h (63%) diff --git a/build/win32/orderfile.txt b/build/win32/orderfile.txt index 4bd71ee00386..b1ad842e3d37 100644 --- a/build/win32/orderfile.txt +++ b/build/win32/orderfile.txt @@ -17078,6 +17078,7 @@ neqo_http3conn_is_zero_rtt ?ClassName@js@@YA?AV?$Handle@PAVPropertyName@js@@@JS@@W4JSProtoKey@@PAUJSContext@@@Z ?FormatStackDump@JS@@YA?AV?$UniquePtr@$$BY0A@DUFreePolicy@JS@@@mozilla@@PAUJSContext@@_N11@Z ?callee@FrameIter@js@@QBEPAVJSFunction@@PAUJSContext@@@Z +?jsprintf@Sprinter@js@@QAA_NPBDZZ ?vprintf@GenericPrinter@js@@QAE_NPBDPAD@Z ?release@Sprinter@js@@QAE?AV?$UniquePtr@$$BY0A@DUFreePolicy@JS@@@mozilla@@XZ ??1Sprinter@js@@QAE@XZ diff --git a/build/win64/orderfile.txt b/build/win64/orderfile.txt index ddc5cf9194ba..3943b3c45536 100644 --- a/build/win64/orderfile.txt +++ b/build/win64/orderfile.txt @@ -16849,6 +16849,7 @@ neqo_http3conn_is_zero_rtt ?ClassName@js@@YA?AV?$Handle@PEAVPropertyName@js@@@JS@@W4JSProtoKey@@PEAUJSContext@@@Z ?FormatStackDump@JS@@YA?AV?$UniquePtr@$$BY0A@DUFreePolicy@JS@@@mozilla@@PEAUJSContext@@_N11@Z ?callee@FrameIter@js@@QEBAPEAVJSFunction@@PEAUJSContext@@@Z +?jsprintf@Sprinter@js@@QEAA_NPEBDZZ ?vprintf@GenericPrinter@js@@QEAA_NPEBDPEAD@Z ?release@Sprinter@js@@QEAA?AV?$UniquePtr@$$BY0A@DUFreePolicy@JS@@@mozilla@@XZ ??1Sprinter@js@@QEAA@XZ diff --git a/js/public/Printer.h b/js/public/Printer.h dissimilarity index 63% index 12e7f33ffb53..9266c689611a 100644 --- a/js/public/Printer.h +++ b/js/public/Printer.h @@ -1,479 +1,233 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * vim: set ts=8 sts=2 et sw=2 tw=80: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef js_Printer_h -#define js_Printer_h - -#include "mozilla/Attributes.h" -#include "mozilla/Range.h" - -#include -#include -#include -#include - -#include "js/TypeDecls.h" -#include "js/Utility.h" -#include "util/Text.h" - -// [SMDOC] *Printer, Sprinter, Fprinter, ... -// -// # Motivation -// -// In many places, we want to have functions which are capable of logging -// various data structures. Previously, we had logging functions for each -// storage, such as using `fwrite`, `printf` or `snprintf`. In additional cases, -// many of these logging options were using a string serializing logging -// function, only to discard the allocated string after it had been copied to a -// file. -// -// GenericPrinter is an answer to avoid excessive amount of temporary -// allocations which are used once, and a way to make logging functions work -// independently of the backend they are used with. -// -// # Design -// -// The GenericPrinter implements most of `put`, `printf`, `vprintf` and -// `putChar` functions, which are implemented using `put` and `putChar` -// functions in the derivative classes. Thus, one does not have to reimplement -// `putString` nor `printf` for each printer. -// -// // Logging the value N to whatever printer is provided such as -// // a file or a string. -// void logN(GenericPrinter& out) { -// out.printf("[Logging] %d\n", this->n); -// } -// -// The printing functions are infallible, from the logging functions -// perspective. If an issue happens while printing, this would be recorded by -// the Printer, and this can be tested using `hadOutOfMemory` function by the -// owner of the Printer instance. -// -// Even in case of failure, printing functions should remain safe to use. Thus -// calling `put` twice in a row is safe even if no check for `hadOutOfMemory` is -// performed. This is necessary to simplify the control flow and avoid bubble up -// failures out of logging functions. -// -// Note, being safe to use does not imply correctness. In case of failure the -// correctness of the printed characters is no longer guarantee. One should use -// `hadOutOfMemory` function to know if any failure happened which might have -// caused incorrect content to be saved. In some cases, such as `Sprinter`, -// where the string buffer can be extracted, the returned value would account -// for checking `hadOutOfMemory`. -// -// # Implementations -// -// The GenericPrinter is a base class where the derivative classes are providing -// different implementations which have their own advantages and disadvantages: -// -// - Fprinter: FILE* printer. Write the content directly to a file. -// -// - Sprinter: System allocator C-string buffer. Write the content to a buffer -// which is reallocated as more content is added. The buffer can then be -// extracted into a C-string or a JSString, respectively using `release` and -// `releaseJS`. -// -// - LSprinter: LifoAlloc C-string rope. Write the content to a list of chunks -// in a LifoAlloc buffer, no-reallocation occur but one should use -// `exportInto` to serialize its content to a Sprinter or a Fprinter. This is -// useful to avoid reallocation copies, while using an existing LifoAlloc. -// -// - EscapePrinter: Wrapper around other printers, to escape characters when -// necessary. -// -// # Print UTF-16 -// -// The GenericPrinter only handle `char` inputs, which is good enough for ASCII -// and Latin1 character sets. However, to handle UTF-16, one should use an -// EscapePrinter as well as a policy for escaping characters. -// -// One might require different escaping policies based on the escape sequences -// and based on the set of accepted character for the content generated. For -// example, JSON does not specify \x escape sequences. -// -// Today the following escape policies exists: -// -// - StringEscape: Produce C-like escape sequences: \, \x and \u. -// - JSONEscape: Produce JSON escape sequences: \ and \u. -// -// An escape policy is defined by 2 functions: -// -// bool isSafeChar(char16_t c): -// Returns whether a character can be printed without being escaped. -// -// void convertInto(GenericPrinter& out, char16_t c): -// Calls the printer with the escape sequence for the character given as -// argument. -// -// To use an escape policy, the printer should be wrapped using an EscapePrinter -// as follows: -// -// { -// // The escaped string is surrounded by double-quotes, escape the double -// // quotes as well. -// StringEscape esc('"'); -// -// // Wrap our existing `GenericPrinter& out` using the `EscapePrinter`. -// EscapePrinter ep(out, esc); -// -// // Append a sequence of characters which might contain UTF-16 characters. -// ep.put(chars); -// } -// - -namespace js { - -class LifoAlloc; - -// Generic printf interface, similar to an ostream in the standard library. -// -// This class is useful to make generic printers which can work either with a -// file backend, with a buffer allocated with an JSContext or a link-list -// of chunks allocated with a LifoAlloc. -class JS_PUBLIC_API GenericPrinter { - protected: - bool hadOOM_; // whether reportOutOfMemory() has been called. - - constexpr GenericPrinter() : hadOOM_(false) {} - - public: - // Puts |len| characters from |s| at the current position. This function might - // silently fail and the error can be tested using `hadOutOfMemory()`. Calling - // this function or any other printing functions after a failures is accepted, - // but the outcome would still remain incorrect and `hadOutOfMemory()` would - // still report any of the previous errors. - virtual void put(const char* s, size_t len) = 0; - inline void put(const char* s) { put(s, strlen(s)); } - - // Put a mozilla::Span / mozilla::Range of Latin1Char or char16_t characters - // in the output. - // - // Note that the char16_t variant is expected to crash unless putChar is - // overriden to handle properly the full set of WTF-16 character set. - virtual void put(mozilla::Span str); - virtual void put(mozilla::Span str); - - // Same as the various put function but only appending a single character. - // - // Note that the char16_t variant is expected to crash unless putChar is - // overriden to handle properly the full set of WTF-16 character set. - virtual inline void putChar(const char c) { put(&c, 1); } - virtual inline void putChar(const JS::Latin1Char c) { putChar(char(c)); } - virtual inline void putChar(const char16_t c) { - MOZ_CRASH("Use an EscapePrinter to handle all characters"); - } - - virtual void putString(JSContext* cx, JSString* str); - - // Prints a formatted string into the buffer. - void printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); - void vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0); - - // In some cases, such as handling JSRopes in a less-quadratic worse-case, - // it might be useful to copy content which has already been generated. - // - // If the buffer is back-readable, then this function should return `true` - // and `putFromIndex` should be implemented to delegate to a `put` call at - // the matching index and the corresponding length. To provide the index - // argument of `putFromIndex`, the `index` method should also be implemented - // to return the index within the inner buffer used by the printer. - virtual bool canPutFromIndex() const { return false; } - - // Append to the current buffer, bytes which have previously been appended - // before. - virtual void putFromIndex(size_t index, size_t length) { - MOZ_CRASH("Calls to putFromIndex should be guarded by canPutFromIndex."); - } - - // When the printer has a seekable buffer and `canPutFromIndex` returns - // `true`, this function can return the `index` of the next character to be - // added to the buffer. - // - // This function is monotonic. Thus, if the printer encounter an - // Out-Of-Memory issue, then the returned index should be the maximal value - // ever returned. - virtual size_t index() const { return 0; } - - // In some printers, this ensure that the content is fully written. - virtual void flush() { /* Do nothing */ - } - - // Report that a string operation failed to get the memory it requested. - virtual void reportOutOfMemory(); - - // Return true if this Sprinter ran out of memory. - virtual bool hadOutOfMemory() const { return hadOOM_; } -}; - -// Sprintf, but with unlimited and automatically allocated buffering. -class JS_PUBLIC_API Sprinter final : public GenericPrinter { - public: - struct InvariantChecker { - const Sprinter* parent; - - explicit InvariantChecker(const Sprinter* p) : parent(p) { - parent->checkInvariants(); - } - - ~InvariantChecker() { parent->checkInvariants(); } - }; - - JSContext* maybeCx; // context executing the decompiler - - private: - static const size_t DefaultSize; -#ifdef DEBUG - bool initialized; // true if this is initialized, use for debug builds -#endif - bool shouldReportOOM; // whether to report OOM to the maybeCx - char* base; // malloc'd buffer address - size_t size; // size of buffer allocated at base - ptrdiff_t offset; // offset of next free char in buffer - - [[nodiscard]] bool realloc_(size_t newSize); - - public: - // JSContext* parameter is optional and can be omitted if the following - // are not used. - // * putString method with JSString - // * QuoteString function with JSString - // * JSONQuoteString function with JSString - // - // If JSContext* parameter is not provided, or shouldReportOOM is false, - // the consumer should manually report OOM on any failure. - explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true); - ~Sprinter(); - - // Initialize this sprinter, returns false on error. - [[nodiscard]] bool init(); - - void checkInvariants() const; - - JS::UniqueChars release(); - JSString* releaseJS(JSContext* cx); - - // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the - // attempt succeeds, return a pointer to the start of that space and adjust - // the internal content. The caller *must* completely fill this space on - // success. - char* reserve(size_t len); - - // Puts |len| characters from |s| at the current position and - // return true on success, false on failure. - virtual void put(const char* s, size_t len) override; - using GenericPrinter::put; // pick up |inline bool put(const char* s);| - - virtual bool canPutFromIndex() const override { return true; } - virtual void putFromIndex(size_t index, size_t length) override { - MOZ_ASSERT(index <= this->index()); - MOZ_ASSERT(index + length <= this->index()); - put(base + index, length); - } - virtual size_t index() const override { return length(); } - - virtual void putString(JSContext* cx, JSString* str) override; - - size_t length() const; - - // When an OOM has already been reported on the Sprinter, this function will - // forward this error to the JSContext given in the Sprinter initialization. - // - // If no JSContext had been provided or the Sprinter is configured to not - // report OOM, then nothing happens. - void forwardOutOfMemory(); -}; - -// Fprinter, print a string directly into a file. -class JS_PUBLIC_API Fprinter final : public GenericPrinter { - private: - FILE* file_; - bool init_; - - public: - explicit Fprinter(FILE* fp); - - constexpr Fprinter() : file_(nullptr), init_(false) {} - -#ifdef DEBUG - ~Fprinter(); -#endif - - // Initialize this printer, returns false on error. - [[nodiscard]] bool init(const char* path); - void init(FILE* fp); - bool isInitialized() const { return file_ != nullptr; } - void flush() override; - void finish(); - - // Puts |len| characters from |s| at the current position and - // return true on success, false on failure. - virtual void put(const char* s, size_t len) override; - using GenericPrinter::put; // pick up |inline bool put(const char* s);| -}; - -// LSprinter, is similar to Sprinter except that instead of using an -// JSContext to allocate strings, it use a LifoAlloc as a backend for the -// allocation of the chunk of the string. -class JS_PUBLIC_API LSprinter final : public GenericPrinter { - private: - struct Chunk { - Chunk* next; - size_t length; - - char* chars() { return reinterpret_cast(this + 1); } - char* end() { return chars() + length; } - }; - - private: - LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations. - Chunk* head_; - Chunk* tail_; - size_t unused_; - - public: - explicit LSprinter(LifoAlloc* lifoAlloc); - ~LSprinter(); - - // Copy the content of the chunks into another printer, such that we can - // flush the content of this printer to a file. - void exportInto(GenericPrinter& out) const; - - // Drop the current string, and let them be free with the LifoAlloc. - void clear(); - - // Puts |len| characters from |s| at the current position and - // return true on success, false on failure. - virtual void put(const char* s, size_t len) override; - using GenericPrinter::put; // pick up |inline bool put(const char* s);| -}; - -// Escaping printers work like any other printer except that any added character -// are checked for escaping sequences. This one would escape a string such that -// it can safely be embedded in a JS string. -template -class JS_PUBLIC_API EscapePrinter final : public GenericPrinter { - size_t lengthOfSafeChars(const char* s, size_t len) { - for (size_t i = 0; i < len; i++) { - if (!esc.isSafeChar(s[i])) { - return i; - } - } - return len; - } - - private: - Delegate& out; - Escape& esc; - - public: - EscapePrinter(Delegate& out, Escape& esc) : out(out), esc(esc) {} - ~EscapePrinter() {} - - using GenericPrinter::put; - void put(const char* s, size_t len) override { - const char* b = s; - while (len) { - size_t index = lengthOfSafeChars(b, len); - if (index) { - out.put(b, index); - len -= index; - b += index; - } - if (len) { - esc.convertInto(out, char16_t(*b)); - len -= 1; - b += 1; - } - } - } - - inline void putChar(const char c) override { - if (esc.isSafeChar(char16_t(c))) { - out.putChar(char(c)); - return; - } - esc.convertInto(out, char16_t(c)); - } - - inline void putChar(const JS::Latin1Char c) override { - if (esc.isSafeChar(char16_t(c))) { - out.putChar(char(c)); - return; - } - esc.convertInto(out, char16_t(c)); - } - - inline void putChar(const char16_t c) override { - if (esc.isSafeChar(c)) { - out.putChar(char(c)); - return; - } - esc.convertInto(out, c); - } - - // Forward calls to delegated printer. - bool canPutFromIndex() const override { return out.canPutFromIndex(); } - void putFromIndex(size_t index, size_t length) final { - out.putFromIndex(index, length); - } - size_t index() const final { return out.index(); } - void flush() final { out.flush(); } - void reportOutOfMemory() final { out.reportOutOfMemory(); } - bool hadOutOfMemory() const final { return out.hadOutOfMemory(); } -}; - -class JS_PUBLIC_API JSONEscape { - public: - inline bool isSafeChar(char16_t c) { - return js::IsAsciiPrintable(c) && c != '"' && c != '\\'; - } - void convertInto(GenericPrinter& out, char16_t c); -}; - -class JS_PUBLIC_API StringEscape { - private: - const char quote = '\0'; - - public: - explicit StringEscape(const char quote = '\0') : quote(quote) {} - - inline bool isSafeChar(char16_t c) { - return js::IsAsciiPrintable(c) && c != quote && c != '\\'; - } - void convertInto(GenericPrinter& out, char16_t c); -}; - -// Map escaped code to the letter/symbol escaped with a backslash. -extern const char js_EscapeMap[]; - -// Return a C-string containing the chars in str, with any non-printing chars -// escaped. If the optional quote parameter is present and is not '\0', quotes -// (as specified by the quote argument) are also escaped, and the quote -// character is appended at the beginning and end of the result string. -// The returned string is guaranteed to contain only ASCII characters. -extern JS_PUBLIC_API JS::UniqueChars QuoteString(JSContext* cx, JSString* str, - char quote = '\0'); - -// Appends the quoted string to the given Sprinter. Follows the same semantics -// as QuoteString from above. -extern JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str, - char quote = '\0'); - -// Appends the quoted string to the given Sprinter. Follows the same -// Appends the JSON quoted string to the given Sprinter. -extern JS_PUBLIC_API void JSONQuoteString(Sprinter* sp, JSString* str); - -// Internal implementation code for QuoteString methods above. -enum class QuoteTarget { String, JSON }; - -template -void JS_PUBLIC_API QuoteString(Sprinter* sp, - const mozilla::Range chars, - char quote = '\0'); - -} // namespace js - -#endif // js_Printer_h +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_Printer_h +#define js_Printer_h + +#include "mozilla/Attributes.h" +#include "mozilla/Range.h" + +#include +#include +#include +#include + +#include "js/TypeDecls.h" +#include "js/Utility.h" + +namespace js { + +class LifoAlloc; + +// Generic printf interface, similar to an ostream in the standard library. +// +// This class is useful to make generic printers which can work either with a +// file backend, with a buffer allocated with an JSContext or a link-list +// of chunks allocated with a LifoAlloc. +class JS_PUBLIC_API GenericPrinter { + protected: + bool hadOOM_; // whether reportOutOfMemory() has been called. + + constexpr GenericPrinter() : hadOOM_(false) {} + + public: + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) = 0; + virtual void flush() { /* Do nothing */ + } + + inline bool put(const char* s) { return put(s, strlen(s)); } + inline bool putChar(const char c) { return put(&c, 1); } + + // Prints a formatted string into the buffer. + bool printf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); + bool vprintf(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0); + + // Report that a string operation failed to get the memory it requested. + virtual void reportOutOfMemory(); + + // Return true if this Sprinter ran out of memory. + virtual bool hadOutOfMemory() const { return hadOOM_; } +}; + +// Sprintf, but with unlimited and automatically allocated buffering. +class JS_PUBLIC_API Sprinter final : public GenericPrinter { + public: + struct InvariantChecker { + const Sprinter* parent; + + explicit InvariantChecker(const Sprinter* p) : parent(p) { + parent->checkInvariants(); + } + + ~InvariantChecker() { parent->checkInvariants(); } + }; + + JSContext* maybeCx; // context executing the decompiler + + private: + static const size_t DefaultSize; +#ifdef DEBUG + bool initialized; // true if this is initialized, use for debug builds +#endif + bool shouldReportOOM; // whether to report OOM to the maybeCx + char* base; // malloc'd buffer address + size_t size; // size of buffer allocated at base + ptrdiff_t offset; // offset of next free char in buffer + + [[nodiscard]] bool realloc_(size_t newSize); + + public: + // JSContext* parameter is optional and can be omitted if the following + // are not used. + // * putString method with JSString + // * QuoteString function with JSString + // * JSONQuoteString function with JSString + // + // If JSContext* parameter is not provided, or shouldReportOOM is false, + // the consumer should manually report OOM on any failure. + explicit Sprinter(JSContext* maybeCx = nullptr, bool shouldReportOOM = true); + ~Sprinter(); + + // Initialize this sprinter, returns false on error. + [[nodiscard]] bool init(); + + void checkInvariants() const; + + const char* string() const { + MOZ_ASSERT(!hadOutOfMemory()); + return base; + } + const char* stringEnd() const { return string() + offset; } + JS::UniqueChars release(); + + // Returns the string at offset |off|. + char* stringAt(ptrdiff_t off) const; + // Returns the char at offset |off|. + char& operator[](size_t off); + + // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the + // attempt succeeds, return a pointer to the start of that space and adjust + // the internal content. The caller *must* completely fill this space on + // success. + char* reserve(size_t len); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| + + // Format the given format/arguments as if by JS_vsmprintf, then put it. + // Return true on success, else return false and report an error (typically + // OOM). + [[nodiscard]] bool jsprintf(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3); + + bool putString(JSString* str); + + ptrdiff_t getOffset() const; + + // Report that a string operation failed to get the memory it requested. The + // first call to this function calls JS_ReportOutOfMemory, and sets this + // Sprinter's outOfMemory flag; subsequent calls do nothing. + virtual void reportOutOfMemory() override; +}; + +// Fprinter, print a string directly into a file. +class JS_PUBLIC_API Fprinter final : public GenericPrinter { + private: + FILE* file_; + bool init_; + + public: + explicit Fprinter(FILE* fp); + + constexpr Fprinter() : file_(nullptr), init_(false) {} + +#ifdef DEBUG + ~Fprinter(); +#endif + + // Initialize this printer, returns false on error. + [[nodiscard]] bool init(const char* path); + void init(FILE* fp); + bool isInitialized() const { return file_ != nullptr; } + void flush() override; + void finish(); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| +}; + +// LSprinter, is similar to Sprinter except that instead of using an +// JSContext to allocate strings, it use a LifoAlloc as a backend for the +// allocation of the chunk of the string. +class JS_PUBLIC_API LSprinter final : public GenericPrinter { + private: + struct Chunk { + Chunk* next; + size_t length; + + char* chars() { return reinterpret_cast(this + 1); } + char* end() { return chars() + length; } + }; + + private: + LifoAlloc* alloc_; // LifoAlloc used as a backend of chunk allocations. + Chunk* head_; + Chunk* tail_; + size_t unused_; + + public: + explicit LSprinter(LifoAlloc* lifoAlloc); + ~LSprinter(); + + // Copy the content of the chunks into another printer, such that we can + // flush the content of this printer to a file. + void exportInto(GenericPrinter& out) const; + + // Drop the current string, and let them be free with the LifoAlloc. + void clear(); + + // Puts |len| characters from |s| at the current position and + // return true on success, false on failure. + virtual bool put(const char* s, size_t len) override; + using GenericPrinter::put; // pick up |inline bool put(const char* s);| +}; + +// Map escaped code to the letter/symbol escaped with a backslash. +extern const char js_EscapeMap[]; + +// Return a C-string containing the chars in str, with any non-printing chars +// escaped. If the optional quote parameter is present and is not '\0', quotes +// (as specified by the quote argument) are also escaped, and the quote +// character is appended at the beginning and end of the result string. +// The returned string is guaranteed to contain only ASCII characters. +extern JS_PUBLIC_API JS::UniqueChars QuoteString(JSContext* cx, JSString* str, + char quote = '\0'); + +// Appends the quoted string to the given Sprinter. Follows the same semantics +// as QuoteString from above. +extern JS_PUBLIC_API bool QuoteString(Sprinter* sp, JSString* str, + char quote = '\0'); + +// Appends the quoted string to the given Sprinter. Follows the same +// Appends the JSON quoted string to the given Sprinter. +extern JS_PUBLIC_API bool JSONQuoteString(Sprinter* sp, JSString* str); + +// Internal implementation code for QuoteString methods above. +enum class QuoteTarget { String, JSON }; + +template +bool JS_PUBLIC_API QuoteString(Sprinter* sp, + const mozilla::Range chars, + char quote = '\0'); + +} // namespace js + +#endif // js_Printer_h diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 0cc97fd3be68..4bfffbcf58dc 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1653,11 +1653,12 @@ static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) { uint8_t* jit_end = nullptr; if (fun->isAsmJSNative() || fun->isWasmWithJitEntry()) { - if (fun->isAsmJSNative()) { + if (fun->isAsmJSNative() && !sprinter.jsprintf("; backend=asmjs\n")) { + return false; + } + if (!sprinter.jsprintf("; backend=wasm\n")) { return false; } - sprinter.printf("; backend=asmjs\n"); - sprinter.printf("; backend=wasm\n"); js::wasm::Instance& inst = fun->wasmInstance(); const js::wasm::Code& code = inst.code(); @@ -1683,11 +1684,17 @@ static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) { js::jit::BaselineScript* baseline = script->hasBaselineScript() ? script->baselineScript() : nullptr; if (ion && ion->method()) { - sprinter.printf("; backend=ion\n"); + if (!sprinter.jsprintf("; backend=ion\n")) { + return false; + } + jit_begin = ion->method()->raw(); jit_end = ion->method()->rawEnd(); } else if (baseline) { - sprinter.printf("; backend=baseline\n"); + if (!sprinter.jsprintf("; backend=baseline\n")) { + return false; + } + jit_begin = baseline->method()->raw(); jit_end = baseline->method()->rawEnd(); } @@ -1714,7 +1721,7 @@ static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) { ReportOutOfMemory(cx); return false; } - sprinter.putString(cx, sresult); + sprinter.putString(sresult); if (args.length() > 1 && args[1].isString()) { RootedString str(cx, args[1].toString()); @@ -1742,7 +1749,7 @@ static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) { fclose(f); } - JSString* str = sprinter.releaseJS(cx); + JSString* str = JS_NewStringCopyZ(cx, sprinter.string()); if (!str) { return false; } @@ -6884,7 +6891,11 @@ static bool GetStringRepresentation(JSContext* cx, unsigned argc, Value* vp) { } str->dumpRepresentation(out, 0); - JSString* rep = out.releaseJS(cx); + if (out.hadOutOfMemory()) { + return false; + } + + JSString* rep = JS_NewStringCopyN(cx, out.string(), out.getOffset()); if (!rep) { return false; } diff --git a/js/src/frontend/ParserAtom.cpp b/js/src/frontend/ParserAtom.cpp index 7ac91ba5984a..f366c959d2a8 100644 --- a/js/src/frontend/ParserAtom.cpp +++ b/js/src/frontend/ParserAtom.cpp @@ -1004,7 +1004,9 @@ UniqueChars ToPrintableStringImpl(mozilla::Range str, if (!sprinter.init()) { return nullptr; } - QuoteString(&sprinter, str, quote); + if (!QuoteString(&sprinter, str, quote)) { + return nullptr; + } return sprinter.release(); } diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index a467421462f9..faa660b9abea 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -909,10 +909,9 @@ void js::Nursery::printCollectionProfile(JS::GCReason reason, stats().maybePrintProfileHeaders(); Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(gcstats::MinorGCProfilePrefix)) { return; } - sprinter.put(gcstats::MinorGCProfilePrefix); size_t pid = getpid(); JSRuntime* runtime = gc->rt; @@ -924,56 +923,55 @@ void js::Nursery::printCollectionProfile(JS::GCReason reason, size_t dedupCount = stats().getStat(gcstats::STAT_STRINGS_DEDUPLICATED); #define PRINT_FIELD_VALUE(_1, _2, format, value) \ - sprinter.printf(" " format, value); - + if (!sprinter.jsprintf(" " format, value)) { \ + return; \ + } FOR_EACH_NURSERY_PROFILE_METADATA(PRINT_FIELD_VALUE) #undef PRINT_FIELD_VALUE printProfileDurations(profileDurations_, sprinter); - JS::UniqueChars str = sprinter.release(); - if (!str) { - return; - } - fputs(str.get(), stats().profileFile()); + fputs(sprinter.string(), stats().profileFile()); } void js::Nursery::printProfileHeader() { Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(gcstats::MinorGCProfilePrefix)) { return; } - sprinter.put(gcstats::MinorGCProfilePrefix); - -#define PRINT_FIELD_NAME(name, width, _1, _2) \ - sprinter.printf(" %-*s", width, name); +#define PRINT_FIELD_NAME(name, width, _1, _2) \ + if (!sprinter.jsprintf(" %-*s", width, name)) { \ + return; \ + } FOR_EACH_NURSERY_PROFILE_METADATA(PRINT_FIELD_NAME) #undef PRINT_FIELD_NAME -#define PRINT_PROFILE_NAME(_1, text) sprinter.printf(" %-6.6s", text); - +#define PRINT_PROFILE_NAME(_1, text) \ + if (!sprinter.jsprintf(" %-6.6s", text)) { \ + return; \ + } FOR_EACH_NURSERY_PROFILE_TIME(PRINT_PROFILE_NAME) #undef PRINT_PROFILE_NAME - sprinter.put("\n"); - - JS::UniqueChars str = sprinter.release(); - if (!str) { + if (!sprinter.put("\n")) { return; } - fputs(str.get(), stats().profileFile()); + + fputs(sprinter.string(), stats().profileFile()); } // static -void js::Nursery::printProfileDurations(const ProfileDurations& times, +bool js::Nursery::printProfileDurations(const ProfileDurations& times, Sprinter& sprinter) { for (auto time : times) { int64_t micros = int64_t(time.ToMicroseconds()); - sprinter.printf(" %6" PRIi64, micros); + if (!sprinter.jsprintf(" %6" PRIi64, micros)) { + return false; + } } - sprinter.put("\n"); + return sprinter.put("\n"); } static constexpr size_t NurserySliceMetadataFormatWidth() { @@ -998,10 +996,9 @@ void js::Nursery::printTotalProfileTimes() { } Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(gcstats::MinorGCProfilePrefix)) { return; } - sprinter.put(gcstats::MinorGCProfilePrefix); size_t pid = getpid(); JSRuntime* runtime = gc->rt; @@ -1012,23 +1009,24 @@ void js::Nursery::printTotalProfileTimes() { MOZ_ASSERT(r > 0 && r < int(sizeof(collections))); #define PRINT_FIELD_VALUE(_1, _2, format, value) \ - sprinter.printf(" " format, value); - + if (!sprinter.jsprintf(" " format, value)) { \ + return; \ + } FOR_EACH_NURSERY_PROFILE_COMMON_METADATA(PRINT_FIELD_VALUE) #undef PRINT_FIELD_VALUE // Use whole width of per-slice metadata to print total slices so the profile // totals that follow line up. size_t width = NurserySliceMetadataFormatWidth(); - sprinter.printf(" %-*s", int(width), collections); - - printProfileDurations(totalDurations_, sprinter); + if (!sprinter.jsprintf(" %-*s", int(width), collections)) { + return; + } - JS::UniqueChars str = sprinter.release(); - if (!str) { + if (!printProfileDurations(totalDurations_, sprinter)) { return; } - fputs(str.get(), stats().profileFile()); + + fputs(sprinter.string(), stats().profileFile()); } void js::Nursery::maybeClearProfileDurations() { diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index adf19e708ef4..a0c10c495b02 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -699,7 +699,7 @@ class Nursery { void maybeClearProfileDurations(); void startProfile(ProfileKey key); void endProfile(ProfileKey key); - static void printProfileDurations(const ProfileDurations& times, + static bool printProfileDurations(const ProfileDurations& times, Sprinter& sprinter); mozilla::TimeStamp collectionStartTime() const; diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 73298e59799a..2166e2d75edf 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -1640,29 +1640,29 @@ void Statistics::printProfileHeader() { } Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(MajorGCProfilePrefix)) { return; } - sprinter.put(MajorGCProfilePrefix); - -#define PRINT_METADATA_NAME(name, width, _1, _2) \ - sprinter.printf(" %-*s", width, name); +#define PRINT_METADATA_NAME(name, width, _1, _2) \ + if (!sprinter.jsprintf(" %-*s", width, name)) { \ + return; \ + } FOR_EACH_GC_PROFILE_METADATA(PRINT_METADATA_NAME) #undef PRINT_METADATA_NAME -#define PRINT_PROFILE_NAME(_1, text, _2) sprinter.printf(" %-6.6s", text); - +#define PRINT_PROFILE_NAME(_1, text, _2) \ + if (!sprinter.jsprintf(" %-6.6s", text)) { \ + return; \ + } FOR_EACH_GC_PROFILE_TIME(PRINT_PROFILE_NAME) #undef PRINT_PROFILE_NAME - sprinter.put("\n"); - - JS::UniqueChars str = sprinter.release(); - if (!str) { + if (!sprinter.put("\n")) { return; } - fputs(str.get(), profileFile()); + + fputs(sprinter.string(), profileFile()); } static TimeDuration SumAllPhaseKinds(const Statistics::PhaseKindTimes& times) { @@ -1681,10 +1681,9 @@ void Statistics::printSliceProfile() { updateTotalProfileTimes(times); Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(MajorGCProfilePrefix)) { return; } - sprinter.put(MajorGCProfilePrefix); size_t pid = getpid(); JSRuntime* runtime = gc->rt; @@ -1696,18 +1695,17 @@ void Statistics::printSliceProfile() { size_t realmCount = zoneStats.realmCount; #define PRINT_FIELD_VALUE(_1, _2, format, value) \ - sprinter.printf(" " format, value); - + if (!sprinter.jsprintf(" " format, value)) { \ + return; \ + } FOR_EACH_GC_PROFILE_METADATA(PRINT_FIELD_VALUE) #undef PRINT_FIELD_VALUE - printProfileTimes(times, sprinter); - - JS::UniqueChars str = sprinter.release(); - if (!str) { + if (!printProfileTimes(times, sprinter)) { return; } - fputs(str.get(), profileFile()); + + fputs(sprinter.string(), profileFile()); } Statistics::ProfileDurations Statistics::getProfileTimes( @@ -1772,14 +1770,16 @@ const char* Statistics::formatBudget(const SliceData& slice) { } /* static */ -void Statistics::printProfileTimes(const ProfileDurations& times, +bool Statistics::printProfileTimes(const ProfileDurations& times, Sprinter& sprinter) { for (auto time : times) { int64_t millis = int64_t(time.ToMilliseconds()); - sprinter.printf(" %6" PRIi64, millis); + if (!sprinter.jsprintf(" %6" PRIi64, millis)) { + return false; + } } - sprinter.put("\n"); + return sprinter.put("\n"); } constexpr size_t SliceMetadataFormatWidth() { @@ -1804,31 +1804,32 @@ void Statistics::printTotalProfileTimes() { } Sprinter sprinter; - if (!sprinter.init()) { + if (!sprinter.init() || !sprinter.put(MajorGCProfilePrefix)) { return; } - sprinter.put(MajorGCProfilePrefix); size_t pid = getpid(); JSRuntime* runtime = gc->rt; #define PRINT_FIELD_VALUE(_1, _2, format, value) \ - sprinter.printf(" " format, value); - + if (!sprinter.jsprintf(" " format, value)) { \ + return; \ + } FOR_EACH_GC_PROFILE_COMMON_METADATA(PRINT_FIELD_VALUE) #undef PRINT_FIELD_VALUE // Use whole width of per-slice metadata to print total slices so the profile // totals that follow line up. size_t width = SliceMetadataFormatWidth(); - sprinter.printf(" %-*s", int(width), formatTotalSlices()); - printProfileTimes(totalTimes_, sprinter); + if (!sprinter.jsprintf(" %-*s", int(width), formatTotalSlices())) { + return; + } - JS::UniqueChars str = sprinter.release(); - if (!str) { + if (!printProfileTimes(totalTimes_, sprinter)) { return; } - fputs(str.get(), profileFile()); + + fputs(sprinter.string(), profileFile()); } const char* Statistics::formatTotalSlices() { diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index f00f5af60af7..3d05b2a8b21e 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -497,7 +497,7 @@ struct Statistics { const char* formatGCFlags(const SliceData& slice); const char* formatBudget(const SliceData& slice); const char* formatTotalSlices(); - static void printProfileTimes(const ProfileDurations& times, + static bool printProfileTimes(const ProfileDurations& times, Sprinter& sprinter); }; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 57ced1357b15..fd56c866cd33 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -7041,8 +7041,8 @@ struct ScriptCountBlockState { ~ScriptCountBlockState() { masm.setPrinter(nullptr); - if (JS::UniqueChars str = printer.release()) { - block.setCode(str.get()); + if (!printer.hadOutOfMemory()) { + block.setCode(printer.string()); } } }; diff --git a/js/src/jit/LIR.cpp b/js/src/jit/LIR.cpp index aa72ca681f1e..fc4d7de9484a 100644 --- a/js/src/jit/LIR.cpp +++ b/js/src/jit/LIR.cpp @@ -457,12 +457,12 @@ UniqueChars LAllocation::toString() const { break; case MIRType::String: // If a JSContext is a available, output the actual string - if (JSContext* cx = TlsContext.get()) { - Sprinter spr(cx); + if (JSContext* maybeCx = TlsContext.get()) { + Sprinter spr(maybeCx); if (!spr.init()) { oomUnsafe.crash("LAllocation::toString()"); } - spr.putString(cx, c->toString()); + spr.putString(c->toString()); buf = spr.release(); } else { buf = JS_smprintf("string"); diff --git a/js/src/jit/PerfSpewer.cpp b/js/src/jit/PerfSpewer.cpp index 102a3b6b8c08..5e06ac9c006c 100644 --- a/js/src/jit/PerfSpewer.cpp +++ b/js/src/jit/PerfSpewer.cpp @@ -495,7 +495,7 @@ void IonPerfSpewer::recordInstruction(MacroAssembler& masm, LInstruction* ins) { if (PerfIROpsEnabled()) { Sprinter buf; CHECK_RETURN(buf.init()); - buf.put(LIRCodeName(op)); + CHECK_RETURN(buf.put(LIRCodeName(op))); ins->printOperands(buf); opcodeStr = buf.release(); } @@ -509,53 +509,53 @@ void IonPerfSpewer::recordInstruction(MacroAssembler& masm, LInstruction* ins) { } #ifdef JS_JITSPEW -static void PrintStackValue(JSContext* cx, StackValue* stackVal, - CompilerFrameInfo& frame, Sprinter& buf) { +static void PrintStackValue(StackValue* stackVal, CompilerFrameInfo& frame, + Sprinter& buf) { switch (stackVal->kind()) { /****** Constant ******/ case StackValue::Constant: { js::Value constantVal = stackVal->constant(); if (constantVal.isInt32()) { - buf.printf("%d", constantVal.toInt32()); + CHECK_RETURN(buf.jsprintf("%d", constantVal.toInt32())); } else if (constantVal.isObjectOrNull()) { - buf.printf("obj:%p", constantVal.toObjectOrNull()); + CHECK_RETURN(buf.jsprintf("obj:%p", constantVal.toObjectOrNull())); } else if (constantVal.isString()) { - buf.put("str:"); - buf.putString(cx, constantVal.toString()); + CHECK_RETURN(buf.put("str:")); + CHECK_RETURN(buf.putString(constantVal.toString())); } else if (constantVal.isNumber()) { - buf.printf("num:%f", constantVal.toNumber()); + CHECK_RETURN(buf.jsprintf("num:%f", constantVal.toNumber())); } else if (constantVal.isSymbol()) { - buf.put("sym:"); + CHECK_RETURN(buf.put("sym:")); constantVal.toSymbol()->dump(buf); } else { - buf.printf("raw:%" PRIx64, constantVal.asRawBits()); + CHECK_RETURN(buf.jsprintf("raw:%" PRIx64, constantVal.asRawBits())); } } break; /****** Register ******/ case StackValue::Register: { Register reg = stackVal->reg().payloadOrValueReg(); - buf.put(reg.name()); + CHECK_RETURN(buf.put(reg.name())); } break; /****** Stack ******/ case StackValue::Stack: - buf.put("stack"); + CHECK_RETURN(buf.put("stack")); break; /****** ThisSlot ******/ case StackValue::ThisSlot: { # ifdef JS_HAS_HIDDEN_SP - buf.put("this"); + CHECK_RETURN(buf.put("this")); # else Address addr = frame.addressOfThis(); - buf.printf("this:%s(%d)", addr.base.name(), addr.offset); + CHECK_RETURN(buf.jsprintf("this:%s(%d)", addr.base.name(), addr.offset)); # endif } break; /****** LocalSlot ******/ case StackValue::LocalSlot: - buf.printf("local:%u", stackVal->localSlot()); + CHECK_RETURN(buf.jsprintf("local:%u", stackVal->localSlot())); break; /****** ArgSlot ******/ case StackValue::ArgSlot: - buf.printf("arg:%u", stackVal->argSlot()); + CHECK_RETURN(buf.jsprintf("arg:%u", stackVal->argSlot())); break; default: @@ -582,7 +582,7 @@ void BaselinePerfSpewer::recordInstruction(JSContext* cx, MacroAssembler& masm, Sprinter buf(cx); CHECK_RETURN(buf.init()); - buf.put(js::CodeName(op)); + CHECK_RETURN(buf.put(js::CodeName(op))); switch (op) { case JSOp::SetName: @@ -593,8 +593,8 @@ void BaselinePerfSpewer::recordInstruction(JSContext* cx, MacroAssembler& masm, case JSOp::GetGName: { // Emit the name used for these ops Rooted name(cx, script->getName(pc)); - buf.put(" "); - buf.putString(cx, name); + CHECK_RETURN(buf.put(" ")); + CHECK_RETURN(buf.putString(name)); } break; default: break; @@ -602,14 +602,14 @@ void BaselinePerfSpewer::recordInstruction(JSContext* cx, MacroAssembler& masm, // Output should be "JSOp (operand1), (operand2), ..." for (unsigned i = 1; i <= numOperands; i++) { - buf.put(" ("); + CHECK_RETURN(buf.put(" (")); StackValue* stackVal = frame.peek(-int(i)); - PrintStackValue(cx, stackVal, frame, buf); + PrintStackValue(stackVal, frame, buf); if (i < numOperands) { - buf.put("),"); + CHECK_RETURN(buf.put("),")); } else { - buf.put(")"); + CHECK_RETURN(buf.put(")")); } } opcodeStr = buf.release(); diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index a8831cf19026..64b27b98ff40 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1959,12 +1959,8 @@ bool RangeAnalysis::analyzeLoop(MBasicBlock* header) { return false; } iterationBound->boundSum.dump(sp); - JS::UniqueChars str = sp.release(); - if (!str) { - return false; - } JitSpew(JitSpew_Range, "computed symbolic bound on backedges: %s", - str.get()); + sp.string()); } #endif diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 0d16fb5566a8..467bb34009c5 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3407,7 +3407,7 @@ static bool Notes(JSContext* cx, unsigned argc, Value* vp) { } } - JSString* str = sprinter.releaseJS(cx); + JSString* str = JS_NewStringCopyZ(cx, sprinter.string()); if (!str) { return false; } @@ -3489,7 +3489,7 @@ static bool DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, } } - return true; + return !sprinter->hadOutOfMemory(); } static bool DisassembleToString(JSContext* cx, unsigned argc, Value* vp) { @@ -3502,7 +3502,16 @@ static bool DisassembleToString(JSContext* cx, unsigned argc, Value* vp) { return false; } - JSString* str = sprinter.releaseJS(cx); + const char* chars = sprinter.string(); + size_t len; + JS::UniqueTwoByteChars buf( + JS::LossyUTF8CharsToNewTwoByteCharsZ( + cx, JS::UTF8Chars(chars, strlen(chars)), &len, js::MallocArena) + .get()); + if (!buf) { + return false; + } + JSString* str = JS_NewUCStringCopyN(cx, buf.get(), len); if (!str) { return false; } @@ -3526,11 +3535,7 @@ static bool Disassemble(JSContext* cx, unsigned argc, Value* vp) { return false; } - JS::UniqueChars str = sprinter.release(); - if (!str) { - return false; - } - fprintf(gOutFile->fp, "%s\n", str.get()); + fprintf(gOutFile->fp, "%s\n", sprinter.string()); args.rval().setUndefined(); return true; } @@ -3588,11 +3593,7 @@ static bool DisassFile(JSContext* cx, unsigned argc, Value* vp) { return false; } - JS::UniqueChars chars = sprinter.release(); - if (!chars) { - return false; - } - fprintf(gOutFile->fp, "%s\n", chars.get()); + fprintf(gOutFile->fp, "%s\n", sprinter.string()); args.rval().setUndefined(); return true; @@ -3655,11 +3656,15 @@ static bool DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) { if (line2 < line1) { if (bupline != line2) { bupline = line2; - sprinter.printf("%s %3u: BACKUP\n", sep, line2); + if (!sprinter.jsprintf("%s %3u: BACKUP\n", sep, line2)) { + return false; + } } } else { if (bupline && line1 == line2) { - sprinter.printf("%s %3u: RESTORE\n", sep, line2); + if (!sprinter.jsprintf("%s %3u: RESTORE\n", sep, line2)) { + return false; + } } bupline = 0; while (line1 < line2) { @@ -3669,7 +3674,9 @@ static bool DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) { return false; } line1++; - sprinter.printf("%s %3u: %s", sep, line1, linebuf); + if (!sprinter.jsprintf("%s %3u: %s", sep, line1, linebuf)) { + return false; + } } } @@ -3682,11 +3689,7 @@ static bool DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) { pc += len; } - JS::UniqueChars str = sprinter.release(); - if (!str) { - return false; - } - fprintf(gOutFile->fp, "%s\n", str.get()); + fprintf(gOutFile->fp, "%s\n", sprinter.string()); } args.rval().setUndefined(); diff --git a/js/src/util/DumpFunctions.cpp b/js/src/util/DumpFunctions.cpp index 5820e31075fd..d609155f4acf 100644 --- a/js/src/util/DumpFunctions.cpp +++ b/js/src/util/DumpFunctions.cpp @@ -285,11 +285,17 @@ static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, if (!funbytes) { return false; } - sp.printf("%d %s(", num, funbytes.get()); + if (!sp.printf("%d %s(", num, funbytes.get())) { + return false; + } } else if (fun) { - sp.printf("%d anonymous(", num); + if (!sp.printf("%d anonymous(", num)) { + return false; + } } else { - sp.printf("%d ", num); + if (!sp.printf("%d ", num)) { + return false; + } } if (showArgs && iter.hasArgs()) { @@ -340,23 +346,28 @@ static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, } if (value) { - sp.printf("%s%s%s%s%s%s", !first ? ", " : "", name ? name : "", - name ? " = " : "", arg.isString() ? "\"" : "", value, - arg.isString() ? "\"" : ""); + if (!sp.printf("%s%s%s%s%s%s", !first ? ", " : "", name ? name : "", + name ? " = " : "", arg.isString() ? "\"" : "", value, + arg.isString() ? "\"" : "")) { + return false; + } first = false; } else { - sp.put( - " \n"); + if (!sp.put(" \n")) { + return false; + } } } } // print filename, line number and column - sp.printf("%s [\"%s\":%u:%u]\n", fun ? ")" : "", - filename ? filename : "", lineno, - column.zeroOriginValue()); + if (!sp.printf("%s [\"%s\":%u:%u]\n", fun ? ")" : "", + filename ? filename : "", lineno, + column.zeroOriginValue())) { + return false; + } // Note: Right now we don't dump the local variables anymore, because // that is hard to support across all the JITs etc. @@ -376,9 +387,13 @@ static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, if (!thisValBytes) { return false; } - sp.printf(" this = %s\n", thisValBytes.get()); + if (!sp.printf(" this = %s\n", thisValBytes.get())) { + return false; + } } else { - sp.put(" \n"); + if (!sp.put(" \n")) { + return false; + } } } } @@ -404,9 +419,10 @@ static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, return false; } cx->clearPendingException(); - sp.put( - " \n"); + if (!sp.put(" \n")) { + return false; + } continue; } @@ -429,12 +445,15 @@ static bool FormatFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, } if (name && value) { - sp.printf(" this.%s = %s%s%s\n", name, v.isString() ? "\"" : "", - value, v.isString() ? "\"" : ""); + if (!sp.printf(" this.%s = %s%s%s\n", name, v.isString() ? "\"" : "", + value, v.isString() ? "\"" : "")) { + return false; + } } else { - sp.put( - " \n"); + if (!sp.put(" \n")) { + return false; + } } } } @@ -453,10 +472,15 @@ static bool FormatWasmFrame(JSContext* cx, const FrameIter& iter, Sprinter& sp, } } - sp.printf("%d %s()", num, nameStr ? nameStr.get() : ""); - sp.printf(" [\"%s\":wasm-function[%u]:0x%x]\n", - iter.filename() ? iter.filename() : "", - iter.wasmFuncIndex(), iter.wasmBytecodeOffset()); + if (!sp.printf("%d %s()", num, nameStr ? nameStr.get() : "")) { + return false; + } + + if (!sp.printf(" [\"%s\":wasm-function[%u]:0x%x]\n", + iter.filename() ? iter.filename() : "", + iter.wasmFuncIndex(), iter.wasmBytecodeOffset())) { + return false; + } MOZ_ASSERT(!cx->isExceptionPending()); return true; @@ -482,7 +506,9 @@ JS::UniqueChars JS::FormatStackDump(JSContext* cx, bool showArgs, } if (num == 0) { - sp.put("JavaScript stack is empty\n"); + if (!sp.put("JavaScript stack is empty\n")) { + return nullptr; + } } return sp.release(); diff --git a/js/src/util/Text.cpp b/js/src/util/Text.cpp index d2ac6dfaa4e3..e26974a10103 100644 --- a/js/src/util/Text.cpp +++ b/js/src/util/Text.cpp @@ -368,7 +368,9 @@ size_t js::PutEscapedStringImpl(char* buffer, size_t bufferSize, buffer = nullptr; } } else if (out) { - out->put(&c, 1); + if (!out->put(&c, 1)) { + return size_t(-1); + } } n++; } diff --git a/js/src/vm/BytecodeUtil.cpp b/js/src/vm/BytecodeUtil.cpp index f663a799dbcc..e55834508956 100644 --- a/js/src/vm/BytecodeUtil.cpp +++ b/js/src/vm/BytecodeUtil.cpp @@ -104,7 +104,9 @@ static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex, [[nodiscard]] static bool DumpIonScriptCounts(Sprinter* sp, HandleScript script, jit::IonScriptCounts* ionCounts) { - sp->printf("IonScript [%zu blocks]:\n", ionCounts->numBlocks()); + if (!sp->jsprintf("IonScript [%zu blocks]:\n", ionCounts->numBlocks())) { + return false; + } for (size_t i = 0; i < ionCounts->numBlocks(); i++) { const jit::IonBlockCounts& block = ionCounts->block(i); @@ -112,16 +114,27 @@ static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex, JS::LimitedColumnNumberZeroOrigin columnNumber; lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()), &columnNumber); - sp->printf("BB #%" PRIu32 " [%05u,%u,%u]", block.id(), block.offset(), - lineNumber, columnNumber.zeroOriginValue()); + if (!sp->jsprintf("BB #%" PRIu32 " [%05u,%u,%u]", block.id(), + block.offset(), lineNumber, + columnNumber.zeroOriginValue())) { + return false; + } if (block.description()) { - sp->printf(" [inlined %s]", block.description()); + if (!sp->jsprintf(" [inlined %s]", block.description())) { + return false; + } } for (size_t j = 0; j < block.numSuccessors(); j++) { - sp->printf(" -> #%" PRIu32, block.successor(j)); + if (!sp->jsprintf(" -> #%" PRIu32, block.successor(j))) { + return false; + } + } + if (!sp->jsprintf(" :: %" PRIu64 " hits\n", block.hitCount())) { + return false; + } + if (!sp->jsprintf("%s\n", block.code())) { + return false; } - sp->printf(" :: %" PRIu64 " hits\n", block.hitCount()); - sp->printf("%s\n", block.code()); } return true; @@ -143,13 +156,19 @@ static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex, return false; } - sp->put(" {"); + if (!sp->put(" {")) { + return false; + } PCCounts* counts = script->maybeGetPCCounts(pc); if (double val = counts ? counts->numExec() : 0.0) { - sp->printf("\"%s\": %.0f", PCCounts::numExecName, val); + if (!sp->jsprintf("\"%s\": %.0f", PCCounts::numExecName, val)) { + return false; + } + } + if (!sp->put("}\n")) { + return false; } - sp->put("}\n"); pc = next; } @@ -197,11 +216,7 @@ bool js::DumpRealmPCCounts(JSContext* cx) { if (!DumpPCCounts(cx, script, &sprinter)) { return false; } - JS::UniqueChars out = sprinter.release(); - if (!out) { - return false; - } - fputs(out.get(), stdout); + fputs(sprinter.string(), stdout); fprintf(stdout, "--- END SCRIPT %s:%u ---\n", filename, script->lineno()); } @@ -1004,47 +1019,78 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, } if (showAll) { - sp->printf("%s:%u\n", script->filename(), unsigned(script->lineno())); + if (!sp->jsprintf("%s:%u\n", script->filename(), + unsigned(script->lineno()))) { + return false; + } } if (pc != nullptr) { - sp->put(" "); + if (!sp->put(" ")) { + return false; + } } if (showAll) { - sp->put("sn stack "); + if (!sp->put("sn stack ")) { + return false; + } + } + if (!sp->put("loc ")) { + return false; } - sp->put("loc "); if (lines) { - sp->put("line"); + if (!sp->put("line")) { + return false; + } + } + if (!sp->put(" op\n")) { + return false; } - sp->put(" op\n"); if (pc != nullptr) { - sp->put(" "); + if (!sp->put(" ")) { + return false; + } } if (showAll) { - sp->put("-- ----- "); + if (!sp->put("-- ----- ")) { + return false; + } + } + if (!sp->put("----- ")) { + return false; } - sp->put("----- "); if (lines) { - sp->put("----"); + if (!sp->put("----")) { + return false; + } + } + if (!sp->put(" --\n")) { + return false; } - sp->put(" --\n"); jsbytecode* next = script->code(); jsbytecode* end = script->codeEnd(); while (next < end) { if (next == script->main()) { - sp->put("main:\n"); + if (!sp->put("main:\n")) { + return false; + } } if (pc != nullptr) { - sp->put(pc == next ? "--> " : " "); + if (!sp->put(pc == next ? "--> " : " ")) { + return false; + } } if (showAll) { if (parser && parser->isReachable(next)) { - sp->printf("%05u ", parser->stackDepthAtPC(next)); + if (!sp->jsprintf("%05u ", parser->stackDepthAtPC(next))) { + return false; + } } else { - sp->put(" "); + if (!sp->put(" ")) { + return false; + } } } unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), @@ -1077,11 +1123,7 @@ JS_PUBLIC_API bool js::DumpPC(JSContext* cx, FILE* fp) { } RootedScript script(cx, iter.script()); bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter); - JS::UniqueChars out = sprinter.release(); - if (!out) { - return false; - } - fprintf(fp, "%s", out.get()); + fprintf(fp, "%s", sprinter.string()); return ok; } @@ -1094,11 +1136,7 @@ JS_PUBLIC_API bool js::DumpScript(JSContext* cx, JSScript* scriptArg, } RootedScript script(cx, scriptArg); bool ok = Disassemble(cx, script, true, &sprinter); - JS::UniqueChars out = sprinter.release(); - if (!out) { - return false; - } - fprintf(fp, "%s", out.get()); + fprintf(fp, "%s", sprinter.string()); return ok; } @@ -1221,9 +1259,13 @@ static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc, BytecodeParser::JumpKind kind) { if (!called) { called = true; - sp->put("\n# "); + if (!sp->put("\n# ")) { + return false; + } } else { - sp->put(", "); + if (!sp->put(", ")) { + return false; + } } switch (kind) { @@ -1231,24 +1273,34 @@ static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc, break; case BytecodeParser::JumpKind::SwitchCase: - sp->put("switch-case "); + if (!sp->put("switch-case ")) { + return false; + } break; case BytecodeParser::JumpKind::SwitchDefault: - sp->put("switch-default "); + if (!sp->put("switch-default ")) { + return false; + } break; case BytecodeParser::JumpKind::TryCatch: - sp->put("try-catch "); + if (!sp->put("try-catch ")) { + return false; + } break; case BytecodeParser::JumpKind::TryFinally: - sp->put("try-finally "); + if (!sp->put("try-finally ")) { + return false; + } break; } - sp->printf("from %s @ %05u", CodeName(JSOp(*pc)), - unsigned(script->pcToOffset(pc))); + if (!sp->jsprintf("from %s @ %05u", CodeName(JSOp(*pc)), + unsigned(script->pcToOffset(pc)))) { + return false; + } return true; }; @@ -1256,7 +1308,9 @@ static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc, return false; } if (called) { - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } } return true; @@ -1277,7 +1331,9 @@ static bool PrintShapeProperties(JSContext* cx, Sprinter* sp, } } - sp->put("{"); + if (!sp->put("{")) { + return false; + } for (size_t i = props.length(); i > 0; i--) { PropertyKey key = props[i - 1]; @@ -1287,14 +1343,17 @@ static bool PrintShapeProperties(JSContext* cx, Sprinter* sp, ReportOutOfMemory(cx); return false; } - sp->putString(cx, str); + if (!sp->putString(str)) { + return false; + } if (i > 1) { - sp->put(", "); + if (!sp->put(", ")) { + return false; + } } } - sp->put("}"); - return true; + return sp->put("}"); } static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, @@ -1306,7 +1365,7 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, } } - size_t before = sp->length(); + size_t before = sp->stringEnd() - sp->string(); bool stackDumped = false; auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() { if (!parser) { @@ -1317,24 +1376,32 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, } stackDumped = true; - size_t after = sp->length(); + size_t after = sp->stringEnd() - sp->string(); MOZ_ASSERT(after >= before); static const size_t stack_column = 40; for (size_t i = after - before; i < stack_column - 1; i++) { - sp->put(" "); + if (!sp->put(" ")) { + return false; + } } - sp->put(" # "); + if (!sp->put(" # ")) { + return false; + } if (!parser->isReachable(pc)) { - sp->put("!!! UNREACHABLE !!!"); + if (!sp->put("!!! UNREACHABLE !!!")) { + return false; + } } else { uint32_t depth = parser->stackDepthAfterPC(pc); for (uint32_t i = 0; i < depth; i++) { if (i) { - sp->put(" "); + if (!sp->put(" ")) { + return false; + } } const OffsetAndDefIndex& offsetAndDefIndex = @@ -1362,11 +1429,17 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op = JSOp(*pc); const JSCodeSpec& cs = CodeSpec(op); const unsigned len = cs.length; - sp->printf("%05u:", loc); + if (!sp->jsprintf("%05u:", loc)) { + return 0; + } if (lines) { - sp->printf("%4u", PCToLineNumber(script, pc)); + if (!sp->jsprintf("%4u", PCToLineNumber(script, pc))) { + return 0; + } + } + if (!sp->jsprintf(" %s", CodeName(op))) { + return 0; } - sp->printf(" %s", CodeName(op)); int i; switch (JOF_TYPE(cs.format)) { @@ -1375,7 +1448,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, case JOF_JUMP: { ptrdiff_t off = GET_JUMP_OFFSET(pc); - sp->printf(" %u (%+d)", unsigned(loc + int(off)), int(off)); + if (!sp->jsprintf(" %u (%+d)", unsigned(loc + int(off)), int(off))) { + return 0; + } break; } @@ -1385,7 +1460,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!ToDisassemblySource(cx, scope, &bytes)) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } break; } @@ -1396,13 +1473,17 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, return 0; } EnvironmentCoordinate ec(pc); - sp->printf(" %s (hops = %u, slot = %u)", bytes.get(), ec.hops(), - ec.slot()); + if (!sp->jsprintf(" %s (hops = %u, slot = %u)", bytes.get(), ec.hops(), + ec.slot())) { + return 0; + } break; } case JOF_DEBUGCOORD: { EnvironmentCoordinate ec(pc); - sp->printf("(hops = %u, slot = %u)", ec.hops(), ec.slot()); + if (!sp->jsprintf("(hops = %u, slot = %u)", ec.hops(), ec.slot())) { + return 0; + } break; } case JOF_ATOM: { @@ -1411,7 +1492,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!bytes) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } break; } case JOF_STRING: { @@ -1420,13 +1503,17 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!bytes) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } break; } case JOF_DOUBLE: { double d = GET_INLINE_VALUE(pc).toDouble(); - sp->printf(" %lf", d); + if (!sp->jsprintf(" %lf", d)) { + return 0; + } break; } @@ -1436,7 +1523,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!bytes) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } break; } @@ -1448,14 +1537,18 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!bytes) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } } break; } case JOF_SHAPE: { SharedShape* shape = script->getShape(pc); - sp->put(" "); + if (!sp->put(" ")) { + return 0; + } if (!PrintShapeProperties(cx, sp, shape)) { return 0; } @@ -1469,7 +1562,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, if (!bytes) { return 0; } - sp->printf(" %s", bytes.get()); + if (!sp->jsprintf(" %s", bytes.get())) { + return 0; + } break; } @@ -1482,7 +1577,10 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, pc2 += JUMP_OFFSET_LEN; high = GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; - sp->printf(" defaultOffset %d low %d high %d", int(off), low, high); + if (!sp->jsprintf(" defaultOffset %d low %d high %d", int(off), low, + high)) { + return 0; + } // Display stack dump before diplaying the offsets for each case. if (!dumpStack()) { @@ -1492,42 +1590,60 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, for (i = low; i <= high; i++) { off = script->tableSwitchCaseOffset(pc, i - low) - script->pcToOffset(pc); - sp->printf("\n\t%d: %d", i, int(off)); + if (!sp->jsprintf("\n\t%d: %d", i, int(off))) { + return 0; + } } break; } case JOF_QARG: - sp->printf(" %u", GET_ARGNO(pc)); + if (!sp->jsprintf(" %u", GET_ARGNO(pc))) { + return 0; + } break; case JOF_LOCAL: - sp->printf(" %u", GET_LOCALNO(pc)); + if (!sp->jsprintf(" %u", GET_LOCALNO(pc))) { + return 0; + } break; case JOF_GCTHING: - sp->printf(" %u", unsigned(GET_GCTHING_INDEX(pc))); + if (!sp->jsprintf(" %u", unsigned(GET_GCTHING_INDEX(pc)))) { + return 0; + } break; case JOF_UINT32: - sp->printf(" %u", GET_UINT32(pc)); + if (!sp->jsprintf(" %u", GET_UINT32(pc))) { + return 0; + } break; case JOF_ICINDEX: - sp->printf(" (ic: %u)", GET_ICINDEX(pc)); + if (!sp->jsprintf(" (ic: %u)", GET_ICINDEX(pc))) { + return 0; + } break; case JOF_LOOPHEAD: - sp->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc), - LoopHeadDepthHint(pc)); + if (!sp->jsprintf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc), + LoopHeadDepthHint(pc))) { + return 0; + } break; case JOF_TWO_UINT8: { int one = (int)GET_UINT8(pc); int two = (int)GET_UINT8(pc + 1); - sp->printf(" %d", one); - sp->printf(" %d", two); + if (!sp->jsprintf(" %d", one)) { + return 0; + } + if (!sp->jsprintf(" %d", two)) { + return 0; + } break; } @@ -1554,7 +1670,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, MOZ_ASSERT(op == JSOp::Int32); i = GET_INT32(pc); print_int: - sp->printf(" %d", i); + if (!sp->jsprintf(" %d", i)) { + return 0; + } break; default: { @@ -1570,7 +1688,9 @@ static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, return 0; } - sp->put("\n"); + if (!sp->put("\n")) { + return 0; + } return len; } @@ -1798,8 +1918,7 @@ bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) { case JSOp::Uint16: case JSOp::Uint24: case JSOp::Int32: - sprinter.printf("%d", GetBytecodeInteger(pc)); - return true; + return sprinter.printf("%d", GetBytecodeInteger(pc)); case JSOp::String: return quote(loadString(pc), '"'); case JSOp::Symbol: { @@ -1955,8 +2074,7 @@ bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) { return write("arguments.length"); case JSOp::GetFrameArg: - sprinter.printf("arguments[%u]", GET_ARGNO(pc)); - return true; + return sprinter.printf("arguments[%u]", GET_ARGNO(pc)); case JSOp::GetActualArg: return write("arguments[") && decompilePCForStackOperand(pc, -1) && @@ -1979,8 +2097,7 @@ bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) { return write("OBJ"); case JSOp::Double: - sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble()); - return true; + return sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble()); case JSOp::Exception: return write("EXCEPTION"); @@ -2165,10 +2282,7 @@ bool ExpressionDecompiler::init() { return sprinter.init(); } -bool ExpressionDecompiler::write(const char* s) { - sprinter.put(s); - return true; -} +bool ExpressionDecompiler::write(const char* s) { return sprinter.put(s); } bool ExpressionDecompiler::write(JSString* str) { if (str == cx->names().dot_this_) { @@ -2177,13 +2291,11 @@ bool ExpressionDecompiler::write(JSString* str) { if (str == cx->names().dot_newTarget_) { return write("new.target"); } - sprinter.putString(cx, str); - return true; + return sprinter.putString(str); } bool ExpressionDecompiler::quote(JSString* s, char quote) { - QuoteString(&sprinter, s, quote); - return true; + return QuoteString(&sprinter, s, quote); } JSAtom* ExpressionDecompiler::loadAtom(jsbytecode* pc) { @@ -2213,7 +2325,16 @@ JSAtom* ExpressionDecompiler::getArg(unsigned slot) { MOZ_CRASH("No binding"); } -UniqueChars ExpressionDecompiler::getOutput() { return sprinter.release(); } +UniqueChars ExpressionDecompiler::getOutput() { + ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0); + auto res = cx->make_pod_array(len + 1); + if (!res) { + return nullptr; + } + js_memcpy(res.get(), sprinter.stringAt(0), len); + res[len] = 0; + return res; +} } // anonymous namespace @@ -2246,8 +2367,7 @@ static bool DecompileAtPCForStackDump( return false; } - sp->put(result.get()); - return true; + return sp->put(result.get()); } #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ @@ -2603,7 +2723,9 @@ size_t JS::GetPCCountScriptCount(JSContext* cx) { [[nodiscard]] static bool JSONStringProperty(Sprinter& sp, JSONPrinter& json, const char* name, JSString* str) { json.beginStringProperty(name); - JSONQuoteString(&sp, str); + if (!JSONQuoteString(&sp, str)) { + return false; + } json.endStringProperty(); return true; } @@ -2682,7 +2804,11 @@ JSString* JS::GetPCCountScriptSummary(JSContext* cx, size_t index) { json.endObject(); - return sp.releaseJS(cx); + if (sp.hadOutOfMemory()) { + return nullptr; + } + + return NewStringCopyZ(cx, sp.string()); } static bool GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, @@ -2842,7 +2968,11 @@ JSString* JS::GetPCCountScriptContents(JSContext* cx, size_t index) { } } - return sp.releaseJS(cx); + if (sp.hadOutOfMemory()) { + return nullptr; + } + + return NewStringCopyZ(cx, sp.string()); } struct CollectedScripts { @@ -2957,6 +3087,10 @@ static bool GenerateLcovInfo(JSContext* cx, JS::Realm* realm, bool isEmpty = true; lcovRealm->exportInto(out, &isEmpty); + if (out.hadOutOfMemory()) { + return false; + } + return true; } @@ -2973,8 +3107,8 @@ JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummaryAll(JSContext* cx, } } - *length = out.length(); - return out.release(); + *length = out.getOffset(); + return js::DuplicateString(cx, out.string(), *length); } JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummary(JSContext* cx, @@ -2988,6 +3122,6 @@ JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummary(JSContext* cx, return nullptr; } - *length = out.length(); - return out.release(); + *length = out.getOffset(); + return js::DuplicateString(cx, out.string(), *length); } diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index 099ade41789b..1a4323c9b462 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -4163,7 +4163,7 @@ static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, buf.printf("Script "); if (JSAtom* name = script->function()->displayAtom()) { - buf.putString(cx, name); + buf.putString(name); buf.printf(" "); } @@ -4171,7 +4171,7 @@ static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, script->lineno()); if (JSAtom* name = innerScript->function()->displayAtom()) { - buf.putString(cx, name); + buf.putString(name); buf.printf(" "); } @@ -4180,14 +4180,10 @@ static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) { buf.printf(" "); - buf.putString(cx, r.front()); + buf.putString(r.front()); } - JS::UniqueChars str = buf.release(); - if (!str) { - return false; - } - printf("%s\n", str.get()); + printf("%s\n", buf.string()); } RootedFunction fun(cx); diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp index 10e188ad4d4f..2d071c56e374 100644 --- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -3247,11 +3247,7 @@ void JSScript::dump(JSContext* cx) { return; } - JS::UniqueChars str = sp.release(); - if (!str) { - return; - } - fprintf(stderr, "%s\n", str.get()); + fprintf(stderr, "%s\n", sp.string()); } void JSScript::dumpRecursive(JSContext* cx) { @@ -3269,11 +3265,7 @@ void JSScript::dumpRecursive(JSContext* cx) { return; } - JS::UniqueChars str = sp.release(); - if (!str) { - return; - } - fprintf(stderr, "%s\n", str.get()); + fprintf(stderr, "%s\n", sp.string()); } static void DumpMutableScriptFlags(js::JSONPrinter& json, @@ -3399,7 +3391,9 @@ bool JSScript::dump(JSContext* cx, JS::Handle script, return false; } - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } if (!Disassemble(cx, script, /* lines = */ true, sp)) { return false; @@ -3425,7 +3419,9 @@ bool JSScript::dump(JSContext* cx, JS::Handle script, JSObject* obj = &gcThing.as(); if (obj->is()) { - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } JS::Rooted fun(cx, &obj->as()); if (fun->isInterpreted()) { @@ -3438,7 +3434,9 @@ bool JSScript::dump(JSContext* cx, JS::Handle script, return false; } } else { - sp->put("[native code]\n"); + if (!sp->put("[native code]\n")) { + return false; + } } } } @@ -3450,10 +3448,13 @@ bool JSScript::dump(JSContext* cx, JS::Handle script, /* static */ bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle script, js::Sprinter* sp) { - sp->put("\nSource notes:\n"); - sp->printf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column", "pc", - "delta", "desc", "args"); - sp->put("---- ---- ------ ----- ------ ---------------- ------\n"); + if (!sp->put("\nSource notes:\n") || + !sp->jsprintf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column", + "pc", "delta", "desc", "args") || + !sp->put("---- ---- ------ ----- ------ ---------------- ------\n")) { + return false; + } + unsigned offset = 0; unsigned lineno = script->lineno(); JS::LimitedColumnNumberZeroOrigin column = script->column(); @@ -3466,8 +3467,10 @@ bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle script, offset += delta; SrcNoteType type = sn->type(); const char* name = sn->name(); - sp->printf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn - notes), lineno, - column.zeroOriginValue(), offset, delta, name); + if (!sp->jsprintf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn - notes), + lineno, column.zeroOriginValue(), offset, delta, name)) { + return false; + } switch (type) { case SrcNoteType::Breakpoint: @@ -3477,21 +3480,28 @@ bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle script, case SrcNoteType::ColSpan: { JS::ColumnNumberOffset colspan = SrcNote::ColSpan::getSpan(sn); - sp->printf(" colspan %u", colspan.value()); + if (!sp->jsprintf(" colspan %u", colspan.value())) { + return false; + } column += colspan; break; } case SrcNoteType::SetLine: lineno = SrcNote::SetLine::getLine(sn, script->lineno()); - sp->printf(" lineno %u", lineno); + if (!sp->jsprintf(" lineno %u", lineno)) { + return false; + } column = JS::LimitedColumnNumberZeroOrigin::zero(); break; case SrcNoteType::SetLineColumn: lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno()); column = SrcNote::SetLineColumn::getColumn(sn); - sp->printf(" lineno %u column %u", lineno, column.zeroOriginValue()); + if (!sp->jsprintf(" lineno %u column %u", lineno, + column.zeroOriginValue())) { + return false; + } break; case SrcNoteType::NewLine: @@ -3501,14 +3511,19 @@ bool JSScript::dumpSrcNotes(JSContext* cx, JS::Handle script, case SrcNoteType::NewLineColumn: column = SrcNote::NewLineColumn::getColumn(sn); - sp->printf(" column %u", column.zeroOriginValue()); + if (!sp->jsprintf(" column %u", column.zeroOriginValue())) { + return false; + } + ++lineno; break; default: MOZ_ASSERT_UNREACHABLE("unrecognized srcnote"); } - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } } return true; @@ -3538,11 +3553,16 @@ static const char* TryNoteName(TryNoteKind kind) { /* static */ bool JSScript::dumpTryNotes(JSContext* cx, JS::Handle script, js::Sprinter* sp) { - sp->put("\nException table:\nkind stack start end\n"); + if (!sp->put( + "\nException table:\nkind stack start end\n")) { + return false; + } for (const js::TryNote& tn : script->trynotes()) { - sp->printf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()), tn.stackDepth, - tn.start, tn.start + tn.length); + if (!sp->jsprintf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()), + tn.stackDepth, tn.start, tn.start + tn.length)) { + return false; + } } return true; } @@ -3550,20 +3570,32 @@ bool JSScript::dumpTryNotes(JSContext* cx, JS::Handle script, /* static */ bool JSScript::dumpScopeNotes(JSContext* cx, JS::Handle script, js::Sprinter* sp) { - sp->put("\nScope notes:\n index parent start end\n"); + if (!sp->put("\nScope notes:\n index parent start end\n")) { + return false; + } for (const ScopeNote& note : script->scopeNotes()) { if (note.index == ScopeNote::NoScopeIndex) { - sp->printf("%8s ", "(none)"); + if (!sp->jsprintf("%8s ", "(none)")) { + return false; + } } else { - sp->printf("%8u ", note.index.index); + if (!sp->jsprintf("%8u ", note.index.index)) { + return false; + } } if (note.parent == ScopeNote::NoScopeIndex) { - sp->printf("%8s ", "(none)"); + if (!sp->jsprintf("%8s ", "(none)")) { + return false; + } } else { - sp->printf("%8u ", note.parent); + if (!sp->jsprintf("%8u ", note.parent)) { + return false; + } + } + if (!sp->jsprintf("%8u %8u\n", note.start, note.start + note.length)) { + return false; } - sp->printf("%8u %8u\n", note.start, note.start + note.length); } return true; } @@ -3571,27 +3603,41 @@ bool JSScript::dumpScopeNotes(JSContext* cx, JS::Handle script, /* static */ bool JSScript::dumpGCThings(JSContext* cx, JS::Handle script, js::Sprinter* sp) { - sp->put("\nGC things:\n index type value\n"); + if (!sp->put("\nGC things:\n index type value\n")) { + return false; + } size_t i = 0; for (JS::GCCellPtr gcThing : script->gcthings()) { - sp->printf("%8zu ", i); + if (!sp->jsprintf("%8zu ", i)) { + return false; + } if (gcThing.is()) { - sp->put("BigInt "); + if (!sp->put("BigInt ")) { + return false; + } gcThing.as().dump(*sp); - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } } else if (gcThing.is()) { - sp->put("Scope "); + if (!sp->put("Scope ")) { + return false; + } JS::Rooted scope(cx, &gcThing.as()); if (!Scope::dumpForDisassemble(cx, scope, *sp, " ")) { return false; } - sp->put("\n"); + if (!sp->put("\n")) { + return false; + } } else if (gcThing.is()) { JSObject* obj = &gcThing.as(); if (obj->is()) { - sp->put("Function "); + if (!sp->put("Function ")) { + return false; + } JS::Rooted fun(cx, &obj->as()); if (fun->displayAtom()) { JS::Rooted name(cx, fun->displayAtom()); @@ -3599,23 +3645,35 @@ bool JSScript::dumpGCThings(JSContext* cx, JS::Handle script, if (!utf8chars) { return false; } - sp->put(utf8chars.get()); + if (!sp->put(utf8chars.get())) { + return false; + } } else { - sp->put("(anonymous)"); + if (!sp->put("(anonymous)")) { + return false; + } } if (fun->hasBaseScript()) { BaseScript* script = fun->baseScript(); - sp->printf(" @ %u:%u\n", script->lineno(), - script->column().zeroOriginValue()); + if (!sp->jsprintf(" @ %u:%u\n", script->lineno(), + script->column().zeroOriginValue())) { + return false; + } } else { - sp->put(" (no script)\n"); + if (!sp->put(" (no script)\n")) { + return false; + } } } else { if (obj->is()) { - sp->put("RegExp "); + if (!sp->put("RegExp ")) { + return false; + } } else { - sp->put("Object "); + if (!sp->put("Object ")) { + return false; + } } JS::Rooted objValue(cx, ObjectValue(*obj)); @@ -3627,24 +3685,39 @@ bool JSScript::dumpGCThings(JSContext* cx, JS::Handle script, if (!utf8chars) { return false; } - sp->put(utf8chars.get()); - sp->put("\n"); + if (!sp->put(utf8chars.get())) { + return false; + } + + if (!sp->put("\n")) { + return false; + } } } else if (gcThing.is()) { JS::Rooted str(cx, &gcThing.as()); if (str->isAtom()) { - sp->put("Atom "); + if (!sp->put("Atom ")) { + return false; + } } else { - sp->put("String "); + if (!sp->put("String ")) { + return false; + } } JS::UniqueChars chars = QuoteString(cx, str, '"'); if (!chars) { return false; } - sp->put(chars.get()); - sp->put("\n"); + if (!sp->put(chars.get())) { + return false; + } + if (!sp->put("\n")) { + return false; + } } else { - sp->put("Unknown\n"); + if (!sp->put("Unknown\n")) { + return false; + } } i++; } diff --git a/js/src/vm/Printer.cpp b/js/src/vm/Printer.cpp index 1f23a1c29fc1..5ad8d20627bd 100644 --- a/js/src/vm/Printer.cpp +++ b/js/src/vm/Printer.cpp @@ -29,8 +29,7 @@ class GenericPrinterPrintfTarget : public mozilla::PrintfTarget { explicit GenericPrinterPrintfTarget(js::GenericPrinter& p) : printer(p) {} bool append(const char* sp, size_t len) override { - printer.put(sp, len); - return true; + return printer.put(sp, len); } private: @@ -48,64 +47,32 @@ void GenericPrinter::reportOutOfMemory() { hadOOM_ = true; } -void GenericPrinter::put(mozilla::Span str) { - if (!str.Length()) { - return; - } - put(reinterpret_cast(&str[0]), str.Length()); -} - -void GenericPrinter::put(mozilla::Span str) { - for (char16_t c : str) { - putChar(c); - } -} - -void GenericPrinter::putString(JSContext* cx, JSString* str) { - StringSegmentRange iter(cx); - if (!iter.init(str)) { - reportOutOfMemory(); - return; - } - JS::AutoCheckCannotGC nogc; - while (!iter.empty()) { - JSLinearString* linear = iter.front(); - if (linear->hasLatin1Chars()) { - put(linear->latin1Range(nogc)); - } else { - put(linear->twoByteRange(nogc)); - } - if (!iter.popFront()) { - reportOutOfMemory(); - return; - } - } -} - -void GenericPrinter::printf(const char* fmt, ...) { +bool GenericPrinter::printf(const char* fmt, ...) { va_list va; va_start(va, fmt); - vprintf(fmt, va); + bool r = vprintf(fmt, va); va_end(va); + return r; } -void GenericPrinter::vprintf(const char* fmt, va_list ap) { +bool GenericPrinter::vprintf(const char* fmt, va_list ap) { // Simple shortcut to avoid allocating strings. if (strchr(fmt, '%') == nullptr) { - put(fmt); + return put(fmt); } GenericPrinterPrintfTarget printer(*this); - (void)printer.vprint(fmt, ap); + if (!printer.vprint(fmt, ap)) { + reportOutOfMemory(); + return false; + } + return true; } const size_t Sprinter::DefaultSize = 64; bool Sprinter::realloc_(size_t newSize) { MOZ_ASSERT(newSize > (size_t)offset); - if (hadOOM_) { - return false; - } char* newBuf = (char*)js_realloc(base, newSize); if (!newBuf) { reportOutOfMemory(); @@ -142,7 +109,6 @@ bool Sprinter::init() { base = js_pod_malloc(DefaultSize); if (!base) { reportOutOfMemory(); - forwardOutOfMemory(); return false; } #ifdef DEBUG @@ -161,12 +127,11 @@ void Sprinter::checkInvariants() const { } UniqueChars Sprinter::release() { + checkInvariants(); if (hadOOM_) { - forwardOutOfMemory(); return nullptr; } - checkInvariants(); char* str = base; base = nullptr; offset = size = 0; @@ -176,58 +141,16 @@ UniqueChars Sprinter::release() { return UniqueChars(str); } -JSString* Sprinter::releaseJS(JSContext* cx) { - if (hadOOM_) { - MOZ_ASSERT_IF(maybeCx, maybeCx == cx); - forwardOutOfMemory(); - return nullptr; - } - - checkInvariants(); - - // Extract Sprinter data. - size_t len = length(); - UniqueChars str(base); - - // Reset Sprinter. - base = nullptr; - offset = 0; - size = 0; -#ifdef DEBUG - initialized = false; -#endif - - // Convert extracted data to a JSString, reusing the current buffer whenever - // possible. - JS::UTF8Chars utf8(str.get(), len); - JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8); - if (encoding == JS::SmallestEncoding::ASCII) { - UniqueLatin1Chars latin1(reinterpret_cast(str.release())); - return js::NewString(cx, std::move(latin1), len); - } - - size_t length; - if (encoding == JS::SmallestEncoding::Latin1) { - UniqueLatin1Chars latin1( - UTF8CharsToNewLatin1CharsZ(cx, utf8, &length, js::StringBufferArena) - .get()); - if (!latin1) { - return nullptr; - } - - return js::NewString(cx, std::move(latin1), length); - } - - MOZ_ASSERT(encoding == JS::SmallestEncoding::UTF16); - - UniqueTwoByteChars utf16( - UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length, js::StringBufferArena) - .get()); - if (!utf16) { - return nullptr; - } +char* Sprinter::stringAt(ptrdiff_t off) const { + MOZ_ASSERT(!hadOutOfMemory()); + MOZ_ASSERT(off >= 0 && (size_t)off < size); + return base + off; +} - return js::NewString(cx, std::move(utf16), length); +char& Sprinter::operator[](size_t off) { + MOZ_ASSERT(!hadOutOfMemory()); + MOZ_ASSERT(off < size); + return *(base + off); } char* Sprinter::reserve(size_t len) { @@ -244,7 +167,7 @@ char* Sprinter::reserve(size_t len) { return sb; } -void Sprinter::put(const char* s, size_t len) { +bool Sprinter::put(const char* s, size_t len) { InvariantChecker ic(this); const char* oldBase = base; @@ -252,36 +175,38 @@ void Sprinter::put(const char* s, size_t len) { char* bp = reserve(len); if (!bp) { - return; + return false; } - // s is within the buffer already + /* s is within the buffer already */ if (s >= oldBase && s < oldEnd) { - // Update the source pointer in case of a realloc-ation. - size_t index = s - oldBase; - s = &base[index]; + /* buffer was realloc'ed */ + if (base != oldBase) { + s = stringAt(s - oldBase); /* this is where it lives now */ + } memmove(bp, s, len); } else { js_memcpy(bp, s, len); } bp[len] = '\0'; + return true; } -void Sprinter::putString(JSContext* cx, JSString* s) { - MOZ_ASSERT(cx); +bool Sprinter::putString(JSString* s) { + MOZ_ASSERT(maybeCx); InvariantChecker ic(this); - JSLinearString* linear = s->ensureLinear(cx); + JSLinearString* linear = s->ensureLinear(maybeCx); if (!linear) { - return; + return false; } size_t length = JS::GetDeflatedUTF8StringLength(linear); char* buffer = reserve(length); if (!buffer) { - return; + return false; } mozilla::DebugOnly written = @@ -289,15 +214,29 @@ void Sprinter::putString(JSContext* cx, JSString* s) { MOZ_ASSERT(written == length); buffer[length] = '\0'; + return true; } -size_t Sprinter::length() const { return size_t(offset); } +ptrdiff_t Sprinter::getOffset() const { return offset; } -void Sprinter::forwardOutOfMemory() { - MOZ_ASSERT(hadOOM_); +void Sprinter::reportOutOfMemory() { + if (hadOOM_) { + return; + } if (maybeCx && shouldReportOOM) { ReportOutOfMemory(maybeCx); } + hadOOM_ = true; +} + +bool Sprinter::jsprintf(const char* format, ...) { + va_list ap; + va_start(ap, format); + + bool r = vprintf(format, ap); + va_end(ap); + + return r; } const char js_EscapeMap[] = { @@ -329,53 +268,118 @@ static const char JSONEscapeMap[] = { }; template -JS_PUBLIC_API void QuoteString(Sprinter* sp, +JS_PUBLIC_API bool QuoteString(Sprinter* sp, const mozilla::Range chars, char quote) { MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0'); + using CharPtr = mozilla::RangedPtr; + + const char* escapeMap = + (target == QuoteTarget::String) ? js_EscapeMap : JSONEscapeMap; + if (quote) { - sp->putChar(quote); + if (!sp->putChar(quote)) { + return false; + } } - if (target == QuoteTarget::String) { - StringEscape esc(quote); - EscapePrinter ep(*sp, esc); - ep.put(chars); - } else { - MOZ_ASSERT(target == QuoteTarget::JSON); - JSONEscape esc; - EscapePrinter ep(*sp, esc); - ep.put(chars); + + const CharPtr end = chars.end(); + + /* Loop control variables: end points at end of string sentinel. */ + for (CharPtr t = chars.begin(); t < end; ++t) { + /* Move t forward from s past un-quote-worthy characters. */ + const CharPtr s = t; + char16_t c = *t; + while (c < 127 && c != '\\') { + if (target == QuoteTarget::String) { + if (!IsAsciiPrintable(c) || c == quote || c == '\t') { + break; + } + } else { + if (c < ' ' || c == '"') { + break; + } + } + + ++t; + if (t == end) { + break; + } + c = *t; + } + + { + ptrdiff_t len = t - s; + ptrdiff_t base = sp->getOffset(); + if (!sp->reserve(len)) { + return false; + } + + for (ptrdiff_t i = 0; i < len; ++i) { + (*sp)[base + i] = char(s[i]); + } + (*sp)[base + len] = '\0'; + } + + if (t == end) { + break; + } + + /* Use escapeMap, \u, or \x only if necessary. */ + const char* escape; + if (!(c >> 8) && c != 0 && + (escape = strchr(escapeMap, int(c))) != nullptr) { + if (!sp->jsprintf("\\%c", escape[1])) { + return false; + } + } else { + /* + * Use \x only if the high byte is 0 and we're in a quoted string, + * because ECMA-262 allows only \u, not \x, in Unicode identifiers + * (see bug 621814). + */ + if (!sp->jsprintf((quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c)) { + return false; + } + } } + + /* Sprint the closing quote and return the quoted string. */ if (quote) { - sp->putChar(quote); + if (!sp->putChar(quote)) { + return false; + } } + + return true; } -template JS_PUBLIC_API void QuoteString( +template JS_PUBLIC_API bool QuoteString( Sprinter* sp, const mozilla::Range chars, char quote); -template JS_PUBLIC_API void QuoteString( +template JS_PUBLIC_API bool QuoteString( Sprinter* sp, const mozilla::Range chars, char quote); -template JS_PUBLIC_API void QuoteString( +template JS_PUBLIC_API bool QuoteString( Sprinter* sp, const mozilla::Range chars, char quote); -template JS_PUBLIC_API void QuoteString( +template JS_PUBLIC_API bool QuoteString( Sprinter* sp, const mozilla::Range chars, char quote); -JS_PUBLIC_API void QuoteString(Sprinter* sp, JSString* str, +JS_PUBLIC_API bool QuoteString(Sprinter* sp, JSString* str, char quote /*= '\0' */) { MOZ_ASSERT(sp->maybeCx); - if (quote) { - sp->putChar(quote); - } - StringEscape esc(quote); - EscapePrinter ep(*sp, esc); - ep.putString(sp->maybeCx, str); - if (quote) { - sp->putChar(quote); + JSLinearString* linear = str->ensureLinear(sp->maybeCx); + if (!linear) { + return false; } + + JS::AutoCheckCannotGC nogc; + return linear->hasLatin1Chars() ? QuoteString( + sp, linear->latin1Range(nogc), quote) + : QuoteString( + sp, linear->twoByteRange(nogc), quote); } JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str, @@ -384,15 +388,24 @@ JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str, if (!sprinter.init()) { return nullptr; } - QuoteString(&sprinter, str, quote); + if (!QuoteString(&sprinter, str, quote)) { + return nullptr; + } return sprinter.release(); } -JS_PUBLIC_API void JSONQuoteString(Sprinter* sp, JSString* str) { +JS_PUBLIC_API bool JSONQuoteString(Sprinter* sp, JSString* str) { MOZ_ASSERT(sp->maybeCx); - JSONEscape esc; - EscapePrinter ep(*sp, esc); - ep.putString(sp->maybeCx, str); + JSLinearString* linear = str->ensureLinear(sp->maybeCx); + if (!linear) { + return false; + } + + JS::AutoCheckCannotGC nogc; + return linear->hasLatin1Chars() ? QuoteString( + sp, linear->latin1Range(nogc), '\0') + : QuoteString( + sp, linear->twoByteRange(nogc), '\0'); } Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); } @@ -430,27 +443,24 @@ void Fprinter::finish() { file_ = nullptr; } -void Fprinter::put(const char* s, size_t len) { - if (hadOutOfMemory()) { - return; - } - +bool Fprinter::put(const char* s, size_t len) { MOZ_ASSERT(file_); int i = fwrite(s, /*size=*/1, /*nitems=*/len, file_); if (size_t(i) != len) { reportOutOfMemory(); - return; + return false; } #ifdef XP_WIN if ((file_ == stderr) && (IsDebuggerPresent())) { UniqueChars buf = DuplicateString(s, len); if (!buf) { reportOutOfMemory(); - return; + return false; } OutputDebugStringA(buf.get()); } #endif + return true; } LSprinter::LSprinter(LifoAlloc* lifoAlloc) @@ -479,11 +489,7 @@ void LSprinter::clear() { hadOOM_ = false; } -void LSprinter::put(const char* s, size_t len) { - if (hadOutOfMemory()) { - return; - } - +bool LSprinter::put(const char* s, size_t len) { // Compute how much data will fit in the current chunk. size_t existingSpaceWrite = 0; size_t overflow = len; @@ -503,7 +509,7 @@ void LSprinter::put(const char* s, size_t len) { last = reinterpret_cast(alloc_->alloc(allocLength)); if (!last) { reportOutOfMemory(); - return; + return false; } } @@ -547,28 +553,7 @@ void LSprinter::put(const char* s, size_t len) { } MOZ_ASSERT(len <= INT_MAX); -} - -void JSONEscape::convertInto(GenericPrinter& out, char16_t c) { - const char* escape = nullptr; - if (!(c >> 8) && c != 0 && - (escape = strchr(JSONEscapeMap, int(c))) != nullptr) { - out.printf("\\%c", escape[1]); - } else { - out.printf("\\u%04X", c); - } -} - -void StringEscape::convertInto(GenericPrinter& out, char16_t c) { - const char* escape = nullptr; - if (!(c >> 8) && c != 0 && - (escape = strchr(js_EscapeMap, int(c))) != nullptr) { - out.printf("\\%c", escape[1]); - } else { - // Use \x only if the high byte is 0 and we're in a quoted string, because - // ECMA-262 allows only \u, not \x, in Unicode identifiers (see bug 621814). - out.printf(!(c >> 8) ? "\\x%02X" : "\\u%04X", c); - } + return true; } } // namespace js diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index f3092b9f0e65..aafa19958d12 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -392,55 +392,88 @@ void Scope::dump() { /* static */ bool Scope::dumpForDisassemble(JSContext* cx, JS::Handle scope, GenericPrinter& out, const char* indent) { - out.put(ScopeKindString(scope->kind())); - out.put(" {"); + if (!out.put(ScopeKindString(scope->kind()))) { + return false; + } + if (!out.put(" {")) { + return false; + } size_t i = 0; for (Rooted bi(cx, BindingIter(scope)); bi; bi++, i++) { if (i == 0) { - out.put("\n"); + if (!out.put("\n")) { + return false; + } } UniqueChars bytes = AtomToPrintableString(cx, bi.name()); if (!bytes) { return false; } - out.put(indent); - out.printf(" %2zu: %s %s ", i, BindingKindString(bi.kind()), bytes.get()); + if (!out.put(indent)) { + return false; + } + if (!out.printf(" %2zu: %s %s ", i, BindingKindString(bi.kind()), + bytes.get())) { + return false; + } switch (bi.location().kind()) { case BindingLocation::Kind::Global: if (bi.isTopLevelFunction()) { - out.put("(global function)\n"); + if (!out.put("(global function)\n")) { + return false; + } } else { - out.put("(global)\n"); + if (!out.put("(global)\n")) { + return false; + } } break; case BindingLocation::Kind::Argument: - out.printf("(arg slot %u)\n", bi.location().argumentSlot()); + if (!out.printf("(arg slot %u)\n", bi.location().argumentSlot())) { + return false; + } break; case BindingLocation::Kind::Frame: - out.printf("(frame slot %u)\n", bi.location().slot()); + if (!out.printf("(frame slot %u)\n", bi.location().slot())) { + return false; + } break; case BindingLocation::Kind::Environment: - out.printf("(env slot %u)\n", bi.location().slot()); + if (!out.printf("(env slot %u)\n", bi.location().slot())) { + return false; + } break; case BindingLocation::Kind::NamedLambdaCallee: - out.put("(named lambda callee)\n"); + if (!out.put("(named lambda callee)\n")) { + return false; + } break; case BindingLocation::Kind::Import: - out.put("(import)\n"); + if (!out.put("(import)\n")) { + return false; + } break; } } if (i > 0) { - out.put(indent); + if (!out.put(indent)) { + return false; + } + } + if (!out.put("}")) { + return false; } - out.put("}"); ScopeIter si(scope); si++; for (; si; si++) { - out.put(" -> "); - out.put(ScopeKindString(si.kind())); + if (!out.put(" -> ")) { + return false; + } + if (!out.put(ScopeKindString(si.kind()))) { + return false; + } } return true; } -- 2.11.4.GIT