| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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 vm_StringBuffer_h |
| #define vm_StringBuffer_h |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/MaybeOneOf.h" |
| |
| #include "jscntxt.h" |
| |
| #include "js/Vector.h" |
| |
| namespace js { |
| |
| /* |
| * String builder that eagerly checks for over-allocation past the maximum |
| * string length. |
| * |
| * Any operation which would exceed the maximum string length causes an |
| * exception report on the context and results in a failed return value. |
| * |
| * Well-sized extractions (which waste no more than 1/4 of their char |
| * buffer space) are guaranteed for strings built by this interface. |
| * See |extractWellSized|. |
| */ |
| class StringBuffer |
| { |
| /* |
| * The Vector's buffer may be either stolen or copied, so we need to use |
| * TempAllocPolicy and account for the memory manually when stealing. |
| */ |
| typedef Vector<Latin1Char, 64> Latin1CharBuffer; |
| typedef Vector<char16_t, 32> TwoByteCharBuffer; |
| |
| ExclusiveContext* cx; |
| |
| /* |
| * If Latin1 strings are enabled, cb starts out as a Latin1CharBuffer. When |
| * a TwoByte char is appended, inflateChars() constructs a TwoByteCharBuffer |
| * and copies the Latin1 chars. |
| */ |
| mozilla::MaybeOneOf<Latin1CharBuffer, TwoByteCharBuffer> cb; |
| |
| /* |
| * Make sure ensureTwoByteChars() is called before calling |
| * infallibleAppend(char16_t). |
| */ |
| mozilla::DebugOnly<bool> hasEnsuredTwoByteChars_; |
| |
| /* Number of reserve()'d chars, see inflateChars. */ |
| size_t reserved_; |
| |
| StringBuffer(const StringBuffer& other) = delete; |
| void operator=(const StringBuffer& other) = delete; |
| |
| MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); } |
| MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); } |
| |
| MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); } |
| MOZ_ALWAYS_INLINE TwoByteCharBuffer& twoByteChars() { return cb.ref<TwoByteCharBuffer>(); } |
| |
| MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const { |
| return cb.ref<Latin1CharBuffer>(); |
| } |
| MOZ_ALWAYS_INLINE const TwoByteCharBuffer& twoByteChars() const { |
| return cb.ref<TwoByteCharBuffer>(); |
| } |
| |
| bool inflateChars(); |
| |
| public: |
| explicit StringBuffer(ExclusiveContext* cx) |
| : cx(cx), hasEnsuredTwoByteChars_(false), reserved_(0) |
| { |
| cb.construct<Latin1CharBuffer>(cx); |
| } |
| |
| inline bool reserve(size_t len) { |
| if (len > reserved_) |
| reserved_ = len; |
| return isLatin1() ? latin1Chars().reserve(len) : twoByteChars().reserve(len); |
| } |
| inline bool resize(size_t len) { |
| return isLatin1() ? latin1Chars().resize(len) : twoByteChars().resize(len); |
| } |
| inline bool empty() const { |
| return isLatin1() ? latin1Chars().empty() : twoByteChars().empty(); |
| } |
| inline size_t length() const { |
| return isLatin1() ? latin1Chars().length() : twoByteChars().length(); |
| } |
| inline char16_t getChar(size_t idx) const { |
| return isLatin1() ? latin1Chars()[idx] : twoByteChars()[idx]; |
| } |
| |
| inline bool ensureTwoByteChars() { |
| if (isLatin1() && !inflateChars()) |
| return false; |
| |
| hasEnsuredTwoByteChars_ = true; |
| return true; |
| } |
| |
| inline bool append(const char16_t c) { |
| if (isLatin1()) { |
| if (c <= JSString::MAX_LATIN1_CHAR) |
| return latin1Chars().append(Latin1Char(c)); |
| if (!inflateChars()) |
| return false; |
| } |
| return twoByteChars().append(c); |
| } |
| inline bool append(Latin1Char c) { |
| return isLatin1() ? latin1Chars().append(c) : twoByteChars().append(c); |
| } |
| inline bool append(char c) { |
| return append(Latin1Char(c)); |
| } |
| |
| inline bool append(const char16_t* begin, const char16_t* end); |
| inline bool append(const char16_t* chars, size_t len) { |
| return append(chars, chars + len); |
| } |
| |
| inline bool append(const Latin1Char* begin, const Latin1Char* end) { |
| return isLatin1() ? latin1Chars().append(begin, end) : twoByteChars().append(begin, end); |
| } |
| inline bool append(const Latin1Char* chars, size_t len) { |
| return append(chars, chars + len); |
| } |
| |
| inline bool append(const JS::ConstCharPtr chars, size_t len) { |
| return append(chars.get(), chars.get() + len); |
| } |
| inline bool appendN(Latin1Char c, size_t n) { |
| return isLatin1() ? latin1Chars().appendN(c, n) : twoByteChars().appendN(c, n); |
| } |
| |
| inline bool append(JSString* str); |
| inline bool append(JSLinearString* str); |
| inline bool appendSubstring(JSString* base, size_t off, size_t len); |
| inline bool appendSubstring(JSLinearString* base, size_t off, size_t len); |
| |
| inline bool append(const char* chars, size_t len) { |
| return append(reinterpret_cast<const Latin1Char*>(chars), len); |
| } |
| |
| template <size_t ArrayLength> |
| bool append(const char (&array)[ArrayLength]) { |
| return append(array, ArrayLength - 1); /* No trailing '\0'. */ |
| } |
| |
| /* Infallible variants usable when the corresponding space is reserved. */ |
| void infallibleAppend(Latin1Char c) { |
| if (isLatin1()) |
| latin1Chars().infallibleAppend(c); |
| else |
| twoByteChars().infallibleAppend(c); |
| } |
| void infallibleAppend(char c) { |
| infallibleAppend(Latin1Char(c)); |
| } |
| void infallibleAppend(const Latin1Char* chars, size_t len) { |
| if (isLatin1()) |
| latin1Chars().infallibleAppend(chars, len); |
| else |
| twoByteChars().infallibleAppend(chars, len); |
| } |
| void infallibleAppend(const char* chars, size_t len) { |
| infallibleAppend(reinterpret_cast<const Latin1Char*>(chars), len); |
| } |
| |
| void infallibleAppendSubstring(JSLinearString* base, size_t off, size_t len); |
| |
| /* |
| * Because inflation is fallible, these methods should only be used after |
| * calling ensureTwoByteChars(). |
| */ |
| void infallibleAppend(const char16_t* chars, size_t len) { |
| MOZ_ASSERT(hasEnsuredTwoByteChars_); |
| twoByteChars().infallibleAppend(chars, len); |
| } |
| void infallibleAppend(char16_t c) { |
| MOZ_ASSERT(hasEnsuredTwoByteChars_); |
| twoByteChars().infallibleAppend(c); |
| } |
| |
| bool isUnderlyingBufferLatin1() const { return isLatin1(); } |
| |
| char16_t* rawTwoByteBegin() { return twoByteChars().begin(); } |
| char16_t* rawTwoByteEnd() { return twoByteChars().end(); } |
| const char16_t* rawTwoByteBegin() const { return twoByteChars().begin(); } |
| const char16_t* rawTwoByteEnd() const { return twoByteChars().end(); } |
| |
| Latin1Char* rawLatin1Begin() { return latin1Chars().begin(); } |
| Latin1Char* rawLatin1End() { return latin1Chars().end(); } |
| const Latin1Char* rawLatin1Begin() const { return latin1Chars().begin(); } |
| const Latin1Char* rawLatin1End() const { return latin1Chars().end(); } |
| |
| /* |
| * Creates a string from the characters in this buffer, then (regardless |
| * whether string creation succeeded or failed) empties the buffer. |
| */ |
| JSFlatString* finishString(); |
| |
| /* Identical to finishString() except that an atom is created. */ |
| JSAtom* finishAtom(); |
| |
| /* |
| * Creates a raw string from the characters in this buffer. The string is |
| * exactly the characters in this buffer (inflated to TwoByte), it is *not* |
| * null-terminated unless the last appended character was '\0'. |
| */ |
| char16_t* stealChars(); |
| }; |
| |
| inline bool |
| StringBuffer::append(const char16_t* begin, const char16_t* end) |
| { |
| MOZ_ASSERT(begin <= end); |
| if (isLatin1()) { |
| while (true) { |
| if (begin >= end) |
| return true; |
| if (*begin > JSString::MAX_LATIN1_CHAR) |
| break; |
| if (!latin1Chars().append(*begin)) |
| return false; |
| ++begin; |
| } |
| if (!inflateChars()) |
| return false; |
| } |
| return twoByteChars().append(begin, end); |
| } |
| |
| inline bool |
| StringBuffer::append(JSLinearString* str) |
| { |
| JS::AutoCheckCannotGC nogc; |
| if (isLatin1()) { |
| if (str->hasLatin1Chars()) |
| return latin1Chars().append(str->latin1Chars(nogc), str->length()); |
| if (!inflateChars()) |
| return false; |
| } |
| return str->hasLatin1Chars() |
| ? twoByteChars().append(str->latin1Chars(nogc), str->length()) |
| : twoByteChars().append(str->twoByteChars(nogc), str->length()); |
| } |
| |
| inline void |
| StringBuffer::infallibleAppendSubstring(JSLinearString* base, size_t off, size_t len) |
| { |
| MOZ_ASSERT(off + len <= base->length()); |
| MOZ_ASSERT_IF(base->hasTwoByteChars(), isTwoByte()); |
| |
| JS::AutoCheckCannotGC nogc; |
| if (base->hasLatin1Chars()) |
| infallibleAppend(base->latin1Chars(nogc) + off, len); |
| else |
| infallibleAppend(base->twoByteChars(nogc) + off, len); |
| } |
| |
| inline bool |
| StringBuffer::appendSubstring(JSLinearString* base, size_t off, size_t len) |
| { |
| MOZ_ASSERT(off + len <= base->length()); |
| |
| JS::AutoCheckCannotGC nogc; |
| if (isLatin1()) { |
| if (base->hasLatin1Chars()) |
| return latin1Chars().append(base->latin1Chars(nogc) + off, len); |
| if (!inflateChars()) |
| return false; |
| } |
| return base->hasLatin1Chars() |
| ? twoByteChars().append(base->latin1Chars(nogc) + off, len) |
| : twoByteChars().append(base->twoByteChars(nogc) + off, len); |
| } |
| |
| inline bool |
| StringBuffer::appendSubstring(JSString* base, size_t off, size_t len) |
| { |
| JSLinearString* linear = base->ensureLinear(cx); |
| if (!linear) |
| return false; |
| |
| return appendSubstring(linear, off, len); |
| } |
| |
| inline bool |
| StringBuffer::append(JSString* str) |
| { |
| JSLinearString* linear = str->ensureLinear(cx); |
| if (!linear) |
| return false; |
| |
| return append(linear); |
| } |
| |
| /* ES5 9.8 ToString, appending the result to the string buffer. */ |
| extern bool |
| ValueToStringBufferSlow(JSContext* cx, const Value& v, StringBuffer& sb); |
| |
| inline bool |
| ValueToStringBuffer(JSContext* cx, const Value& v, StringBuffer& sb) |
| { |
| if (v.isString()) |
| return sb.append(v.toString()); |
| |
| return ValueToStringBufferSlow(cx, v, sb); |
| } |
| |
| /* ES5 9.8 ToString for booleans, appending the result to the string buffer. */ |
| inline bool |
| BooleanToStringBuffer(bool b, StringBuffer& sb) |
| { |
| return b ? sb.append("true") : sb.append("false"); |
| } |
| |
| } /* namespace js */ |
| |
| #endif /* vm_StringBuffer_h */ |