зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1839396 part 16.1 - Reimplement QuoteString using EscapePrinter. r=arai,mgaudet
`QuoteString` is today only available as a mean to serialize a `JSString` to a `Sprinter`, or by making an extra temporary allocation whch is most likely discarded once the serialized content has been used once. This implementation provide a generic escaping mechanism, named `EscapePrinter`, which can be used on top of any existing Printer class, and with any escaping logic such as different quotes, or different quotation marks if needed. The different quoting strategies are implemented using a class which provides `isSafeChar` and `convertInto` to normalize the `JSString` input characters into characters which are properly encoded for the Printer. This change keep the original `QuoteString` behavior, while replacing its fallible implementation by an infallible implementation. `QuoteString` had an undocummented side-effect of linearizing the strings while reading them, which causes memory allocation and potential GC failures. This change replaces the `ensureLinear` call by the new `GenericPrinter::putString` function which relies on `StringSegmentRange` to iterate over the string fragments and serialize the content which does not have the same side-effects. If performance issue arise from this modification, calls to `ensureLinear` should be added before the `QuoteString` calls. Differential Revision: https://phabricator.services.mozilla.com/D183758
This commit is contained in:
Родитель
dcb8feecbc
Коммит
4802861d94
|
@ -42,7 +42,15 @@ class JS_PUBLIC_API GenericPrinter {
|
|||
// 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)); }
|
||||
inline void putChar(const char c) { put(&c, 1); }
|
||||
virtual void put(mozilla::Span<const JS::Latin1Char> str);
|
||||
// Would crash unless putChar is overriden, like in EscapePrinter.
|
||||
virtual void put(mozilla::Span<const char16_t> str);
|
||||
|
||||
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 putAsciiPrintable(mozilla::Span<const JS::Latin1Char> str);
|
||||
virtual void putAsciiPrintable(mozilla::Span<const char16_t> str);
|
||||
|
@ -56,6 +64,8 @@ class JS_PUBLIC_API GenericPrinter {
|
|||
putChar(char(c));
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -143,7 +153,7 @@ class JS_PUBLIC_API Sprinter final : public GenericPrinter {
|
|||
}
|
||||
virtual size_t index() const override { return length(); }
|
||||
|
||||
void putString(JSContext* cx, JSString* str);
|
||||
virtual void putString(JSContext* cx, JSString* str) override;
|
||||
|
||||
size_t length() const;
|
||||
|
||||
|
@ -219,6 +229,97 @@ class JS_PUBLIC_API LSprinter final : public GenericPrinter {
|
|||
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 <typename Delegate, typename Escape>
|
||||
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:
|
||||
bool isSafeChar(char16_t 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) {}
|
||||
|
||||
bool isSafeChar(char16_t c);
|
||||
void convertInto(GenericPrinter& out, char16_t c);
|
||||
};
|
||||
|
||||
// Map escaped code to the letter/symbol escaped with a backslash.
|
||||
extern const char js_EscapeMap[];
|
||||
|
||||
|
|
|
@ -48,7 +48,24 @@ void GenericPrinter::reportOutOfMemory() {
|
|||
hadOOM_ = true;
|
||||
}
|
||||
|
||||
void GenericPrinter::putAsciiPrintable(mozilla::Span<const JS::Latin1Char> str) {
|
||||
void GenericPrinter::put(mozilla::Span<const JS::Latin1Char> str) {
|
||||
if (!str.Length()) {
|
||||
return;
|
||||
}
|
||||
put(reinterpret_cast<const char*>(&str[0]), str.Length());
|
||||
}
|
||||
|
||||
void GenericPrinter::put(mozilla::Span<const char16_t> str) {
|
||||
for (char16_t c : str) {
|
||||
putChar(c);
|
||||
}
|
||||
}
|
||||
|
||||
void GenericPrinter::putAsciiPrintable(
|
||||
mozilla::Span<const JS::Latin1Char> str) {
|
||||
if (!str.Length()) {
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (char c: str) {
|
||||
MOZ_ASSERT(IsAsciiPrintable(c));
|
||||
|
@ -63,6 +80,24 @@ void GenericPrinter::putAsciiPrintable(mozilla::Span<const char16_t> str) {
|
|||
}
|
||||
}
|
||||
|
||||
void GenericPrinter::putString(JSContext* cx, JSString* str) {
|
||||
StringSegmentRange iter(cx);
|
||||
if (!iter.init(str)) {
|
||||
reportOutOfMemory();
|
||||
}
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
while (!iter.empty()) {
|
||||
JSLinearString* linear = iter.front();
|
||||
if (linear->hasLatin1Chars()) {
|
||||
put(linear->latin1Range(nogc));
|
||||
}
|
||||
else {
|
||||
put(linear->twoByteRange(nogc));
|
||||
}
|
||||
iter.popFront();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericPrinter::printf(const char* fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
|
@ -315,63 +350,19 @@ JS_PUBLIC_API bool QuoteString(Sprinter* sp,
|
|||
char quote) {
|
||||
MOZ_ASSERT_IF(target == QuoteTarget::JSON, quote == '\0');
|
||||
|
||||
using CharPtr = mozilla::RangedPtr<const CharT>;
|
||||
|
||||
const char* escapeMap =
|
||||
(target == QuoteTarget::String) ? js::js_EscapeMap : JSONEscapeMap;
|
||||
|
||||
if (quote) {
|
||||
sp->putChar(quote);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
mozilla::Span<const CharT> span(s.get(), t - s);
|
||||
sp->putAsciiPrintable(span);
|
||||
|
||||
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) {
|
||||
sp->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).
|
||||
*/
|
||||
sp->printf((quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* Sprint the closing quote and return the quoted string. */
|
||||
if (quote) {
|
||||
sp->putChar(quote);
|
||||
}
|
||||
|
@ -394,16 +385,17 @@ template JS_PUBLIC_API bool QuoteString<QuoteTarget::JSON, char16_t>(
|
|||
JS_PUBLIC_API bool QuoteString(Sprinter* sp, JSString* str,
|
||||
char quote /*= '\0' */) {
|
||||
MOZ_ASSERT(sp->maybeCx);
|
||||
JSLinearString* linear = str->ensureLinear(sp->maybeCx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
if (quote) {
|
||||
sp->putChar(quote);
|
||||
}
|
||||
StringEscape esc(quote);
|
||||
EscapePrinter ep(*sp, esc);
|
||||
ep.putString(sp->maybeCx, str);
|
||||
if (quote) {
|
||||
sp->putChar(quote);
|
||||
}
|
||||
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
return linear->hasLatin1Chars() ? QuoteString<QuoteTarget::String>(
|
||||
sp, linear->latin1Range(nogc), quote)
|
||||
: QuoteString<QuoteTarget::String>(
|
||||
sp, linear->twoByteRange(nogc), quote);
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str,
|
||||
|
@ -420,16 +412,10 @@ JS_PUBLIC_API UniqueChars QuoteString(JSContext* cx, JSString* str,
|
|||
|
||||
JS_PUBLIC_API bool JSONQuoteString(Sprinter* sp, JSString* str) {
|
||||
MOZ_ASSERT(sp->maybeCx);
|
||||
JSLinearString* linear = str->ensureLinear(sp->maybeCx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
return linear->hasLatin1Chars() ? QuoteString<QuoteTarget::JSON>(
|
||||
sp, linear->latin1Range(nogc), '\0')
|
||||
: QuoteString<QuoteTarget::JSON>(
|
||||
sp, linear->twoByteRange(nogc), '\0');
|
||||
JSONEscape esc;
|
||||
EscapePrinter ep(*sp, esc);
|
||||
ep.putString(sp->maybeCx, str);
|
||||
return true;
|
||||
}
|
||||
|
||||
Fprinter::Fprinter(FILE* fp) : file_(nullptr), init_(false) { init(fp); }
|
||||
|
@ -586,4 +572,34 @@ void LSprinter::put(const char* s, size_t len) {
|
|||
MOZ_ASSERT(len <= INT_MAX);
|
||||
}
|
||||
|
||||
bool JSONEscape::isSafeChar(char16_t c) {
|
||||
return js::IsAsciiPrintable(c) && c != '"' && c != '\\';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool StringEscape::isSafeChar(char16_t c) {
|
||||
return js::IsAsciiPrintable(c) && c != quote && 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);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
|
Загрузка…
Ссылка в новой задаче