// @author: Andrei Alexandrescu (aalexandre)
// String type.
-#ifndef FOLLY_BASE_FBSTRING_H_
-#define FOLLY_BASE_FBSTRING_H_
+#pragma once
#include <atomic>
#include <limits>
namespace folly {
#endif
-// Different versions of gcc/clang support different versions of
-// the address sanitizer attribute. Unfortunately, this attribute
-// has issues when inlining is used, so disable that as well.
#if defined(__clang__)
# if __has_feature(address_sanitizer)
-# if __has_attribute(__no_sanitize__)
-# define FBSTRING_DISABLE_ADDRESS_SANITIZER \
- __attribute__((__no_sanitize__("address"), __noinline__))
-# elif __has_attribute(__no_address_safety_analysis__)
-# define FBSTRING_DISABLE_ADDRESS_SANITIZER \
- __attribute__((__no_address_safety_analysis__, __noinline__))
-# elif __has_attribute(__no_sanitize_address__)
-# define FBSTRING_DISABLE_ADDRESS_SANITIZER \
- __attribute__((__no_sanitize_address__, __noinline__))
-# endif
+# define FBSTRING_SANITIZE_ADDRESS
# endif
#elif defined (__GNUC__) && \
+ (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ >= 5)) && \
__SANITIZE_ADDRESS__
-# define FBSTRING_DISABLE_ADDRESS_SANITIZER \
- __attribute__((__no_address_safety_analysis__, __noinline__))
+# define FBSTRING_SANITIZE_ADDRESS
#endif
-#ifndef FBSTRING_DISABLE_ADDRESS_SANITIZER
-# define FBSTRING_DISABLE_ADDRESS_SANITIZER
+
+// When compiling with ASan, always heap-allocate the string even if
+// it would fit in-situ, so that ASan can detect access to the string
+// buffer after it has been invalidated (destroyed, resized, etc.).
+// Note that this flag doesn't remove support for in-situ strings, as
+// that would break ABI-compatibility and wouldn't allow linking code
+// compiled with this flag with code compiled without.
+#ifdef FBSTRING_SANITIZE_ADDRESS
+# define FBSTRING_DISABLE_SSO true
+#else
+# define FBSTRING_DISABLE_SSO false
#endif
namespace fbstring_detail {
// initialized (the beginning of the expanded portion). The caller
// is expected to fill the expanded area appropriately.
// If expGrowth is true, exponential growth is guaranteed.
+ // It is not guaranteed not to reallocate even if size() + delta <
+ // capacity(), so all references to the buffer are invalidated.
Char* expand_noinit(size_t delta, bool expGrowth);
// Expands the string by one character and sets the last character
// to c.
* to extract capacity/category.
*/
template <class Char> class fbstring_core {
+protected:
+// It's MSVC, so we just have to guess ... and allow an override
+#ifdef _MSC_VER
+# ifdef FOLLY_ENDIAN_BE
+ static constexpr auto kIsLittleEndian = false;
+# else
+ static constexpr auto kIsLittleEndian = true;
+# endif
+#else
+ static constexpr auto kIsLittleEndian =
+ __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__;
+#endif
public:
fbstring_core() noexcept { reset(); }
fbstring_core(fbstring_core&& goner) noexcept {
// Take goner's guts
ml_ = goner.ml_;
- if (goner.category() != Category::isSmall) {
- // Clean goner's carcass
- goner.reset();
- }
+ // Clean goner's carcass
+ goner.reset();
}
- // NOTE(agallagher): The word-aligned copy path copies bytes which are
- // outside the range of the string, and makes address sanitizer unhappy,
- // so just disable it on this function.
- fbstring_core(const Char *const data, const size_t size)
- FBSTRING_DISABLE_ADDRESS_SANITIZER {
+ fbstring_core(const Char *const data,
+ const size_t size,
+ bool disableSSO = FBSTRING_DISABLE_SSO) {
#ifndef NDEBUG
#ifndef _LIBSTDCXX_FBSTRING
SCOPE_EXIT {
assert(this->size() == size);
- assert(memcmp(this->data(), data, size * sizeof(Char)) == 0);
+ assert(size == 0 || memcmp(this->data(), data, size * sizeof(Char)) == 0);
};
#endif
#endif
// Simplest case first: small strings are bitblitted
- if (size <= maxSmallSize) {
+ if (!disableSSO && size <= maxSmallSize) {
// Layout is: Char* data_, size_t size_, size_t capacity_
static_assert(sizeof(*this) == sizeof(Char*) + 2 * sizeof(size_t),
"fbstring has unexpected size");
// If data is aligned, use fast word-wise copying. Otherwise,
// use conservative memcpy.
- if (reinterpret_cast<size_t>(data) & (sizeof(size_t) - 1)) {
- fbstring_detail::pod_copy(data, data + size, small_);
- } else {
- // Copy one word at a time
+ // The word-wise path reads bytes which are outside the range of
+ // the string, and makes ASan unhappy, so we disable it when
+ // compiling with ASan.
+#ifndef FBSTRING_SANITIZE_ADDRESS
+ if ((reinterpret_cast<size_t>(data) & (sizeof(size_t) - 1)) == 0) {
const size_t byteSize = size * sizeof(Char);
constexpr size_t wordWidth = sizeof(size_t);
switch ((byteSize + wordWidth - 1) / wordWidth) { // Number of words.
case 0:
break;
}
+ } else
+#endif
+ {
+ if (size != 0) {
+ fbstring_detail::pod_copy(data, data + size, small_);
+ }
}
setSmallSize(size);
} else {
}
}
- void reserve(size_t minCapacity) {
+ void reserve(size_t minCapacity, bool disableSSO = FBSTRING_DISABLE_SSO) {
if (category() == Category::isLarge) {
// Ensure unique
if (RefCounted::refs(ml_.data_) > 1) {
}
} else {
assert(category() == Category::isSmall);
- if (minCapacity > maxMediumSize) {
- // large
- auto const newRC = RefCounted::create(& minCapacity);
- auto const size = smallSize();
- // Also copies terminator.
- fbstring_detail::pod_copy(small_, small_ + size + 1, newRC->data_);
- ml_.data_ = newRC->data_;
- ml_.size_ = size;
- ml_.setCapacity(minCapacity, Category::isLarge);
- assert(capacity() >= minCapacity);
- } else if (minCapacity > maxSmallSize) {
+ if (!disableSSO && minCapacity <= maxSmallSize) {
+ // small
+ // Nothing to do, everything stays put
+ } else if (minCapacity <= maxMediumSize) {
// medium
// Don't forget to allocate one extra Char for the terminating null
auto const allocSizeBytes =
ml_.size_ = size;
ml_.setCapacity(allocSizeBytes / sizeof(Char) - 1, Category::isMedium);
} else {
- // small
- // Nothing to do, everything stays put
+ // large
+ auto const newRC = RefCounted::create(& minCapacity);
+ auto const size = smallSize();
+ // Also copies terminator.
+ fbstring_detail::pod_copy(small_, small_ + size + 1, newRC->data_);
+ ml_.data_ = newRC->data_;
+ ml_.size_ = size;
+ ml_.setCapacity(minCapacity, Category::isLarge);
+ assert(capacity() >= minCapacity);
}
}
assert(capacity() >= minCapacity);
}
- Char * expand_noinit(const size_t delta, bool expGrowth = false) {
+ Char * expand_noinit(const size_t delta,
+ bool expGrowth = false,
+ bool disableSSO = FBSTRING_DISABLE_SSO) {
// Strategy is simple: make room, then change size
assert(capacity() >= size());
size_t sz, newSz;
if (category() == Category::isSmall) {
sz = smallSize();
newSz = sz + delta;
- if (FBSTRING_LIKELY(newSz <= maxSmallSize)) {
+ if (!disableSSO && FBSTRING_LIKELY(newSz <= maxSmallSize)) {
setSmallSize(newSz);
return small_ + sz;
}
}
void push_back(Char c) {
- *expand_noinit(1, /* expGrowth */ true) = c;
+ *expand_noinit(1, /* expGrowth = */ true) = c;
}
size_t size() const {
> const_reverse_iterator;
static const size_type npos; // = size_type(-1)
+ typedef std::true_type IsRelocatable;
private:
static void procrustes(size_type& n, size_type nmax) {
}
fbstring_detail::pod_copy(
- s, s + n, store_.expand_noinit(n, /* expGrowth */ true));
+ s, s + n, store_.expand_noinit(n, /* expGrowth = */ true));
assert(size() == oldSize + n);
return *this;
}
}
basic_fbstring& append(size_type n, value_type c) {
- resize(size() + n, c);
+ Invariant checker(*this);
+ auto pData = store_.expand_noinit(n, /* expGrowth = */ true);
+ fbstring_detail::pod_fill(pData, pData + n, c);
return *this;
}
Invariant checker(*this);
// s can alias this, we need to use pod_move.
- if (size() >= n) {
+ if (n == 0) {
+ resize(0);
+ } else if (size() >= n) {
fbstring_detail::pod_move(s, s + n, store_.mutable_data());
resize(n);
assert(size() == n);
private:
template <int i> class Selector {};
- iterator insertImplDiscr(const_iterator p,
+ iterator insertImplDiscr(const_iterator i,
size_type n, value_type c, Selector<1>) {
Invariant checker(*this);
- auto const pos = p - begin();
- assert(p >= begin() && p <= end());
- if (capacity() - size() < n) {
- const size_type sz = p - begin();
- reserve(size() + n);
- p = begin() + sz;
- }
- const iterator oldEnd = end();
- if (n < size_type(oldEnd - p)) {
- append(oldEnd - n, oldEnd);
- // Also copies terminator.
- fbstring_detail::pod_move(&*p, &*oldEnd - n + 1, begin() + pos + n);
- std::fill(begin() + pos, begin() + pos + n, c);
- } else {
- append(n - (end() - p), c);
- append(iterator(p), oldEnd);
- std::fill(iterator(p), oldEnd, c);
- }
- return begin() + pos;
+ assert(i >= begin() && i <= end());
+ const size_type pos = i - begin();
+
+ auto oldSize = size();
+ store_.expand_noinit(n, /* expGrowth = */ true);
+ auto b = begin();
+ fbstring_detail::pod_move(b + pos, b + oldSize, b + pos + n);
+ fbstring_detail::pod_fill(b + pos, b + pos + n, c);
+
+ return b + pos;
}
template<class InputIter>
template <class FwdIterator>
iterator insertImpl(const_iterator i,
- FwdIterator s1, FwdIterator s2, std::forward_iterator_tag) {
+ FwdIterator s1,
+ FwdIterator s2,
+ std::forward_iterator_tag) {
Invariant checker(*this);
+ assert(i >= begin() && i <= end());
const size_type pos = i - begin();
- const typename std::iterator_traits<FwdIterator>::difference_type n2 =
- std::distance(s1, s2);
- assert(n2 >= 0);
- using namespace fbstring_detail;
- assert(pos <= size());
-
- const typename std::iterator_traits<FwdIterator>::difference_type maxn2 =
- capacity() - size();
- if (maxn2 < n2) {
- // realloc the string
- reserve(size() + n2);
- i = begin() + pos;
- }
- if (pos + n2 <= size()) {
- const iterator tailBegin = end() - n2;
- store_.expand_noinit(n2);
- fbstring_detail::pod_copy(tailBegin, tailBegin + n2, end() - n2);
- std::copy(const_reverse_iterator(tailBegin), const_reverse_iterator(i),
- reverse_iterator(tailBegin + n2));
- std::copy(s1, s2, begin() + pos);
- } else {
- FwdIterator t = s1;
- const size_type old_size = size();
- std::advance(t, old_size - pos);
- const size_t newElems = std::distance(t, s2);
- store_.expand_noinit(n2);
- std::copy(t, s2, begin() + old_size);
- fbstring_detail::pod_copy(data() + pos, data() + old_size,
- begin() + old_size + newElems);
- std::copy(s1, t, begin() + pos);
- }
- return begin() + pos;
+ auto n = std::distance(s1, s2);
+ assert(n >= 0);
+
+ auto oldSize = size();
+ store_.expand_noinit(n, /* expGrowth = */ true);
+ auto b = begin();
+ fbstring_detail::pod_move(b + pos, b + oldSize, b + pos + n);
+ std::copy(s1, s2, b + pos);
+
+ return b + pos;
}
template <class InputIterator>
enforce(pos <= size(), std::__throw_out_of_range, "");
procrustes(n, size() - pos);
- fbstring_detail::pod_copy(
- data() + pos,
- data() + pos + n,
- s);
+ if (n != 0) {
+ fbstring_detail::pod_copy(data() + pos, data() + pos + n, s);
+ }
return n;
}
// Handle interaction with different C++ standard libraries, which
// expect these types to be in different namespaces.
-#define FOLLY_FBSTRING_HASH1(T) \
- template <> \
- struct hash< ::folly::basic_fbstring<T> > { \
- size_t operator()(const ::folly::fbstring& s) const { \
- return ::folly::hash::fnv32_buf(s.data(), s.size()); \
- } \
+#define FOLLY_FBSTRING_HASH1(T) \
+ template <> \
+ struct hash<::folly::basic_fbstring<T>> { \
+ size_t operator()(const ::folly::basic_fbstring<T>& s) const { \
+ return ::folly::hash::fnv32_buf(s.data(), s.size() * sizeof(T)); \
+ } \
};
// The C++11 standard says that these four are defined
#pragma GCC diagnostic pop
-#undef FBSTRING_DISABLE_ADDRESS_SANITIZER
+#undef FBSTRING_DISABLE_SSO
+#undef FBSTRING_SANITIZE_ADDRESS
#undef throw
#undef FBSTRING_LIKELY
#undef FBSTRING_UNLIKELY
#undef NDEBUG
#undef FOLLY_DEFINED_NDEBUG_FOR_FBSTRING
#endif // FOLLY_DEFINED_NDEBUG_FOR_FBSTRING
-
-#endif // FOLLY_BASE_FBSTRING_H_