Bound formatted string lengths with `ABSL_ASSUME` across `absl/strings`. In hardened builds, constructing an `absl::string_view` checks that its length does not exceed `absl::string_view::max_size()`, emitting a comparison, branch, and trap instruction (`ud2`) whenever the compiler cannot statically upper-bound the runtime length returned by out-of-line formatting routines such as `FastIntToBuffer`, `SixDigitsToBuffer`, and `FastHexToBufferZeroPad16`, or tracked across `FormatSinkImpl` and `IntDigits` scratch buffers. Similarly, `IntegerToString` and `FloatToString` only asserted `size < buf_size` in debug builds, preventing release builds from proving that formatted outputs fit within `std::string` small-string capacity. Add `ABSL_ASSUME` bounds on scratch-buffer string lengths across `AlphaNum` (`str_cat.h`), `FormatHex`/`FormatDec`/`IntegerToString`/`FloatToString` (`str_cat.cc`), `substitute_internal::Arg` (`substitute.h`, `substitute.cc`), `SourceLocation` stringification (`stringify_sink.h`, `stringify_sink.cc`), `FormatSinkImpl::Flush` (`str_format/extension.h`), and `IntDigits` (`str_format/arg.cc`). Static ThinLTO release builds eliminate 331 to 593 `ud2` traps and reduce `.text` by an additional 2.5 KB to 5.4 KB per test binary. PiperOrigin-RevId: 1003911333
diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc index b5c7e19..e86e534 100644 --- a/absl/strings/ascii.cc +++ b/absl/strings/ascii.cc
@@ -14,13 +14,17 @@ #include "absl/strings/ascii.h" +#include <algorithm> #include <climits> #include <cstddef> #include <cstring> #include <string> +#include <utility> #include "absl/base/config.h" #include "absl/base/nullability.h" +#include "absl/strings/resize_and_overwrite.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -227,6 +231,18 @@ return AsciiStrCaseFold<true>(dst, src, n); } +std::string AsciiStrToLower(std::string&& s) { + std::string result = std::move(s); + absl::AsciiStrToLower(&result); + return result; +} + +std::string AsciiStrToUpper(std::string&& s) { + std::string result = std::move(s); + absl::AsciiStrToUpper(&result); + return result; +} + static constexpr size_t ValidateAsciiCasefold() { constexpr size_t num_chars = 1 + CHAR_MAX - CHAR_MIN; size_t incorrect_index = 0; @@ -258,11 +274,58 @@ return ascii_internal::AsciiStrCaseFold<false>(p, p, s->size()); } +std::string AsciiStrToLower(absl::string_view s) { + std::string result; + StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { + ascii_internal::AsciiStrCaseFold<false>(buf, s.data(), s.size()); + return buf_size; + }); + return result; +} + void AsciiStrToUpper(std::string* absl_nonnull s) { char* p = &(*s)[0]; return ascii_internal::AsciiStrCaseFold<true>(p, p, s->size()); } +std::string AsciiStrToUpper(absl::string_view s) { + std::string result; + StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { + ascii_internal::AsciiStrCaseFold<true>(buf, s.data(), s.size()); + return buf_size; + }); + return result; +} + +absl::string_view StripLeadingAsciiWhitespace(absl::string_view str) { + auto it = std::find_if_not(str.begin(), str.end(), absl::ascii_isspace); + return str.substr(static_cast<size_t>(it - str.begin())); +} + +void StripLeadingAsciiWhitespace(std::string* absl_nonnull str) { + auto it = std::find_if_not(str->begin(), str->end(), absl::ascii_isspace); + str->erase(str->begin(), it); +} + +absl::string_view StripTrailingAsciiWhitespace(absl::string_view str) { + auto it = std::find_if_not(str.rbegin(), str.rend(), absl::ascii_isspace); + return str.substr(0, static_cast<size_t>(str.rend() - it)); +} + +void StripTrailingAsciiWhitespace(std::string* absl_nonnull str) { + auto it = std::find_if_not(str->rbegin(), str->rend(), absl::ascii_isspace); + str->erase(static_cast<size_t>(str->rend() - it)); +} + +absl::string_view StripAsciiWhitespace(absl::string_view str) { + return StripTrailingAsciiWhitespace(StripLeadingAsciiWhitespace(str)); +} + +void StripAsciiWhitespace(std::string* absl_nonnull str) { + StripTrailingAsciiWhitespace(str); + StripLeadingAsciiWhitespace(str); +} + void RemoveExtraAsciiWhitespace(std::string* absl_nonnull str) { auto stripped = StripAsciiWhitespace(*str);
diff --git a/absl/strings/ascii.h b/absl/strings/ascii.h index d6252d6..5989f9f 100644 --- a/absl/strings/ascii.h +++ b/absl/strings/ascii.h
@@ -82,6 +82,10 @@ void AsciiStrToUpper(char* absl_nonnull dst, const char* absl_nullable src, size_t n); +ABSL_ATTRIBUTE_NOINLINE std::string AsciiStrToLower(std::string&& s); + +ABSL_ATTRIBUTE_NOINLINE std::string AsciiStrToUpper(std::string&& s); + } // namespace ascii_internal // ascii_isalpha() @@ -185,26 +189,18 @@ } // Converts the characters in `s` to lowercase, changing the contents of `s`. -void AsciiStrToLower(std::string* absl_nonnull s); +ABSL_ATTRIBUTE_NOINLINE void AsciiStrToLower(std::string* absl_nonnull s); // Creates a lowercase string from a given absl::string_view. -[[nodiscard]] inline std::string AsciiStrToLower(absl::string_view s) { - std::string result; - StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { - ascii_internal::AsciiStrToLower(buf, s.data(), s.size()); - return buf_size; - }); - return result; -} +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::string AsciiStrToLower( + absl::string_view s); // Creates a lowercase string from a given std::string&&. // // (Template is used to lower priority of this overload.) template <int&... DoNotSpecify> [[nodiscard]] inline std::string AsciiStrToLower(std::string&& s) { - std::string result = std::move(s); - absl::AsciiStrToLower(&result); - return result; + return ascii_internal::AsciiStrToLower(std::move(s)); } // ascii_toupper() @@ -216,71 +212,52 @@ } // Converts the characters in `s` to uppercase, changing the contents of `s`. -void AsciiStrToUpper(std::string* absl_nonnull s); +ABSL_ATTRIBUTE_NOINLINE void AsciiStrToUpper(std::string* absl_nonnull s); // Creates an uppercase string from a given absl::string_view. -[[nodiscard]] inline std::string AsciiStrToUpper(absl::string_view s) { - std::string result; - StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { - ascii_internal::AsciiStrToUpper(buf, s.data(), s.size()); - return buf_size; - }); - return result; -} +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::string AsciiStrToUpper( + absl::string_view s); // Creates an uppercase string from a given std::string&&. // // (Template is used to lower priority of this overload.) template <int&... DoNotSpecify> [[nodiscard]] inline std::string AsciiStrToUpper(std::string&& s) { - std::string result = std::move(s); - absl::AsciiStrToUpper(&result); - return result; + return ascii_internal::AsciiStrToUpper(std::move(s)); } // Returns absl::string_view with whitespace stripped from the beginning of the // given string_view. -[[nodiscard]] inline absl::string_view StripLeadingAsciiWhitespace( - absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND) { - auto it = std::find_if_not(str.begin(), str.end(), absl::ascii_isspace); - return str.substr(static_cast<size_t>(it - str.begin())); -} +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE absl::string_view +StripLeadingAsciiWhitespace( + absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND); // Strips in place whitespace from the beginning of the given string. -inline void StripLeadingAsciiWhitespace(std::string* absl_nonnull str) { - auto it = std::find_if_not(str->begin(), str->end(), absl::ascii_isspace); - str->erase(str->begin(), it); -} +ABSL_ATTRIBUTE_NOINLINE void StripLeadingAsciiWhitespace( + std::string* absl_nonnull str); // Returns absl::string_view with whitespace stripped from the end of the given // string_view. -[[nodiscard]] inline absl::string_view StripTrailingAsciiWhitespace( - absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND) { - auto it = std::find_if_not(str.rbegin(), str.rend(), absl::ascii_isspace); - return str.substr(0, static_cast<size_t>(str.rend() - it)); -} +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE absl::string_view +StripTrailingAsciiWhitespace( + absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND); // Strips in place whitespace from the end of the given string -inline void StripTrailingAsciiWhitespace(std::string* absl_nonnull str) { - auto it = std::find_if_not(str->rbegin(), str->rend(), absl::ascii_isspace); - str->erase(static_cast<size_t>(str->rend() - it)); -} +ABSL_ATTRIBUTE_NOINLINE void StripTrailingAsciiWhitespace( + std::string* absl_nonnull str); // Returns absl::string_view with whitespace stripped from both ends of the // given string_view. -[[nodiscard]] inline absl::string_view StripAsciiWhitespace( - absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND) { - return StripTrailingAsciiWhitespace(StripLeadingAsciiWhitespace(str)); -} +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE absl::string_view StripAsciiWhitespace( + absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND); // Strips in place whitespace from both ends of the given string -inline void StripAsciiWhitespace(std::string* absl_nonnull str) { - StripTrailingAsciiWhitespace(str); - StripLeadingAsciiWhitespace(str); -} +ABSL_ATTRIBUTE_NOINLINE void StripAsciiWhitespace( + std::string* absl_nonnull str); // Removes leading, trailing, and consecutive internal whitespace. -void RemoveExtraAsciiWhitespace(std::string* absl_nonnull str); +ABSL_ATTRIBUTE_NOINLINE void RemoveExtraAsciiWhitespace( + std::string* absl_nonnull str); ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index efe9041..ebebfe9 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc
@@ -979,7 +979,7 @@ bool ok; StringResizeAndOverwrite( - *dest, dest_len, [src, slen, unbase64, &ok](char* buf, size_t buf_size) { + *dest, dest_len, [src, slen, &unbase64, &ok](char* buf, size_t buf_size) { size_t len; ok = Base64UnescapeInternal(src, slen, buf, buf_size, unbase64, &len); if (!ok) {
diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index 66ff22c..1502ff1 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h
@@ -94,7 +94,7 @@ // std::string s = "foo\rbar\tbaz\010\011\012\013\014\x0d\n"; // std::string escaped_s = absl::CEscape(s); // EXPECT_EQ(escaped_s, "foo\\rbar\\tbaz\\010\\t\\n\\013\\014\\r\\n"); -std::string CEscape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string CEscape(absl::string_view src); // CHexEscape() // @@ -107,7 +107,7 @@ // std::string s = "foo\rbar\tbaz\010\011\012\013\014\x0d\n"; // std::string escaped_s = absl::CHexEscape(s); // EXPECT_EQ(escaped_s, "foo\\rbar\\tbaz\\x08\\t\\n\\x0b\\x0c\\r\\n"); -std::string CHexEscape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string CHexEscape(absl::string_view src); // Utf8SafeCEscape() // @@ -115,21 +115,21 @@ // octal sequences, and passing through UTF-8 characters without conversion. // I.e., when encountering any bytes with their high bit set, this function // will not escape those values, whether or not they are valid UTF-8. -std::string Utf8SafeCEscape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string Utf8SafeCEscape(absl::string_view src); // Utf8SafeCHexEscape() // // Escapes a `src` string using C-style escape sequences, escaping bytes as // hexadecimal sequences, and passing through UTF-8 characters without // conversion. -std::string Utf8SafeCHexEscape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string Utf8SafeCHexEscape(absl::string_view src); // Base64Escape() // // Encodes a `src` string into a base64-encoded `dest` string with padding // characters. This function conforms with RFC 4648 section 4 (base64) and RFC // 2045. -std::string Base64Escape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string Base64Escape(absl::string_view src); [[deprecated( "Use the string-returning version of " "Base64Escape()")]] ABSL_REFACTOR_INLINE inline void @@ -142,7 +142,7 @@ // Encodes a `src` string into a base64 string, like Base64Escape() does, but // outputs '-' instead of '+' and '_' instead of '/', and does not pad `dest`. // This function conforms with RFC 4648 section 5 (base64url). -std::string WebSafeBase64Escape(absl::string_view src); +ABSL_ATTRIBUTE_NOINLINE std::string WebSafeBase64Escape(absl::string_view src); [[deprecated( "Use the string-returning version of " "WebSafeBase64Escape()")]] ABSL_REFACTOR_INLINE inline void @@ -157,7 +157,8 @@ // `src` contains invalid characters, `dest` is cleared and returns `false`. // If padding is included (note that `Base64Escape()` does produce it), it must // be correct. In the padding, '=' and '.' are treated identically. -bool Base64Unescape(absl::string_view src, std::string* absl_nonnull dest); +ABSL_ATTRIBUTE_NOINLINE bool Base64Unescape(absl::string_view src, + std::string* absl_nonnull dest); // WebSafeBase64Unescape() // @@ -167,8 +168,8 @@ // `false`. If padding is included (note that `WebSafeBase64Escape()` does not // produce it), it must be correct. In the padding, '=' and '.' are treated // identically. -bool WebSafeBase64Unescape(absl::string_view src, - std::string* absl_nonnull dest); +ABSL_ATTRIBUTE_NOINLINE bool WebSafeBase64Unescape( + absl::string_view src, std::string* absl_nonnull dest); // HexStringToBytes() // @@ -185,13 +186,13 @@ // `from.size()/2`. The input must be valid hexadecimal data, otherwise the // return value is unspecified. [[deprecated("Use the HexStringToBytes() that returns a bool")]] -std::string HexStringToBytes(absl::string_view from); +ABSL_ATTRIBUTE_NOINLINE std::string HexStringToBytes(absl::string_view from); // BytesToHexString() // // Converts binary data into an ASCII text string, returning a string of size // `2*from.size()`. -std::string BytesToHexString(absl::string_view from); +ABSL_ATTRIBUTE_NOINLINE std::string BytesToHexString(absl::string_view from); // UrlEscape() // @@ -211,7 +212,8 @@ // absl::UrlEscape("gift for mom & dad")); // assert(url == // "https://www.google.com/search?q=gift%20for%20mom%20%26%20dad"); -[[nodiscard]] std::string UrlEscape(absl::string_view input); +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::string UrlEscape( + absl::string_view input); // UrlUnescape() // @@ -222,7 +224,8 @@ // // UrlUnescape() is identical to UrlUnescapePlus() except that it does not // unescape '+' to ' '. -[[nodiscard]] std::optional<std::string> UrlUnescape(absl::string_view input); +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::optional<std::string> UrlUnescape( + absl::string_view input); // UrlEscapePlus() // @@ -252,14 +255,15 @@ // absl::UrlEscapePlus( // "gift for mom & dad")); // assert(url == "https://www.google.com/search?q=gift+for+mom+%26+dad"); -[[nodiscard]] std::string UrlEscapePlus(absl::string_view input); +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::string UrlEscapePlus( + absl::string_view input); // UrlUnescapePlus() // // Performs the inverse transformation of UrlEscapePlus(). It returns // `std::nullopt` if any % is not followed by two hexadecimal digits. -[[nodiscard]] std::optional<std::string> UrlUnescapePlus( - absl::string_view input); +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::optional<std::string> +UrlUnescapePlus(absl::string_view input); ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/strings/internal/ostringstream.cc b/absl/strings/internal/ostringstream.cc index 5ac41cc..e848c15 100644 --- a/absl/strings/internal/ostringstream.cc +++ b/absl/strings/internal/ostringstream.cc
@@ -17,7 +17,10 @@ #include <cassert> #include <cstddef> #include <ios> +#include <ostream> #include <streambuf> +#include <string> +#include <utility> #include "absl/base/config.h" @@ -25,6 +28,26 @@ ABSL_NAMESPACE_BEGIN namespace strings_internal { +OStringStream::OStringStream(std::string* str) + : std::ostream(&buf_), buf_(str) {} + +OStringStream::OStringStream(OStringStream&& that) + : std::ostream(std::move(static_cast<std::ostream&>(that))), + buf_(that.buf_) { + rdbuf(&buf_); +} + +OStringStream& OStringStream::operator=(OStringStream&& that) { + std::ostream::operator=(std::move(static_cast<std::ostream&>(that))); + buf_ = that.buf_; + rdbuf(&buf_); + return *this; +} + +OStringStream::~OStringStream() = default; + +OStringStream::Streambuf::~Streambuf() = default; + OStringStream::Streambuf::int_type OStringStream::Streambuf::overflow(int c) { assert(str_); if (!std::streambuf::traits_type::eq_int_type(
diff --git a/absl/strings/internal/ostringstream.h b/absl/strings/internal/ostringstream.h index c0e237d..1139baf 100644 --- a/absl/strings/internal/ostringstream.h +++ b/absl/strings/internal/ostringstream.h
@@ -20,8 +20,8 @@ #include <ostream> #include <streambuf> #include <string> -#include <utility> +#include "absl/base/attributes.h" #include "absl/base/config.h" namespace absl { @@ -69,19 +69,10 @@ // // The destructor of OStringStream doesn't use the std::string. It's OK to // destroy the std::string before the stream. - explicit OStringStream(std::string* str) - : std::ostream(&buf_), buf_(str) {} - OStringStream(OStringStream&& that) - : std::ostream(std::move(static_cast<std::ostream&>(that))), - buf_(that.buf_) { - rdbuf(&buf_); - } - OStringStream& operator=(OStringStream&& that) { - std::ostream::operator=(std::move(static_cast<std::ostream&>(that))); - buf_ = that.buf_; - rdbuf(&buf_); - return *this; - } + ABSL_ATTRIBUTE_NOINLINE explicit OStringStream(std::string* str); + ABSL_ATTRIBUTE_NOINLINE OStringStream(OStringStream&& that); + ABSL_ATTRIBUTE_NOINLINE OStringStream& operator=(OStringStream&& that); + ABSL_ATTRIBUTE_NOINLINE ~OStringStream() override; std::string* str() { return buf_.str(); } const std::string* str() const { return buf_.str(); } @@ -93,6 +84,7 @@ explicit Streambuf(std::string* str) : str_(str) {} Streambuf(const Streambuf&) = default; Streambuf& operator=(const Streambuf&) = default; + ~Streambuf() override; std::string* str() { return str_; } const std::string* str() const { return str_; }
diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 2125be3..ac43f41 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc
@@ -172,14 +172,20 @@ // The printed value including the '-' sign if available. // For inputs of value `0`, this will return "0" - string_view with_neg_and_zero() const { return {start_, size_}; } + string_view with_neg_and_zero() const { + const size_t len = size_; + ABSL_ASSUME(len <= sizeof(storage_)); + return {start_, len}; + } // The printed value not including the '-' sign. // For inputs of value `0`, this will return "". string_view without_neg_or_zero() const { static_assert('-' < '0', "The check below verifies both."); - size_t advance = start_[0] <= '0' ? 1 : 0; - return {start_ + advance, size_ - advance}; + const size_t advance = start_[0] <= '0' ? 1 : 0; + const size_t len = size_ - advance; + ABSL_ASSUME(len <= sizeof(storage_)); + return {start_ + advance, len}; } bool is_negative() const { return start_[0] == '-'; }
diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index 0e0b262..4387c20 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h
@@ -24,6 +24,7 @@ #include <string> #include "absl/base/config.h" +#include "absl/base/optimization.h" #include "absl/strings/internal/str_format/output.h" #include "absl/strings/string_view.h" @@ -70,7 +71,9 @@ ~FormatSinkImpl() { Flush(); } void Flush() { - raw_.Write(string_view(buf_, static_cast<size_t>(pos_ - buf_))); + const size_t len = static_cast<size_t>(pos_ - buf_); + ABSL_ASSUME(len <= sizeof(buf_)); + raw_.Write(string_view(buf_, len)); pos_ = buf_; }
diff --git a/absl/strings/internal/str_join_internal.h b/absl/strings/internal/str_join_internal.h index 8f9c11a..56b71ed 100644 --- a/absl/strings/internal/str_join_internal.h +++ b/absl/strings/internal/str_join_internal.h
@@ -41,6 +41,7 @@ #include <tuple> #include <type_traits> #include <utility> +#include <vector> #include "absl/algorithm/container.h" #include "absl/base/config.h" @@ -229,44 +230,65 @@ // string to avoid the need to resize while appending. To do this, the iterator // range will be traversed twice: once to calculate the total needed size, and // then again to copy the elements and delimiters to the output string. +template <typename Iterator, typename Value> +struct IsContiguousIteratorOf + : std::integral_constant< + bool, + (std::is_pointer_v<Iterator> && + std::is_same_v<std::remove_cv_t<std::remove_pointer_t<Iterator>>, + Value>) || + std::is_same_v<Iterator, typename std::vector<Value>::iterator> || + std::is_same_v<Iterator, + typename std::vector<Value>::const_iterator>> {}; + template <typename Iterator, typename = std::enable_if_t< base_internal::IsAtLeastForwardIterator<Iterator>::value>> std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view sep, NoFormatter) { - std::string result; - if (start != end) { - // Sums size - auto&& start_value = *start; - // Use uint64_t to prevent size_t overflow. We assume it is not possible for - // in memory strings to overflow a uint64_t. - uint64_t result_size = start_value.size(); - for (Iterator it = start; ++it != end;) { - result_size += sep.size(); - result_size += (*it).size(); - } + if constexpr (IsContiguousIteratorOf<Iterator, absl::string_view>::value) { + const auto count = static_cast<size_t>(end - start); + return JoinStringViews(count == 0 ? nullptr : std::addressof(*start), count, + sep); + } else if constexpr (IsContiguousIteratorOf<Iterator, std::string>::value) { + const auto count = static_cast<size_t>(end - start); + return JoinStrings(count == 0 ? nullptr : std::addressof(*start), count, + sep); + } else { + std::string result; + if (start != end) { + // Sums size + auto&& start_value = *start; + // Use uint64_t to prevent size_t overflow. We assume it is not possible + // for in memory strings to overflow a uint64_t. + uint64_t result_size = start_value.size(); + for (Iterator it = start; ++it != end;) { + result_size += sep.size(); + result_size += (*it).size(); + } - if (result_size > 0) { - constexpr uint64_t kMaxSize = - uint64_t{(std::numeric_limits<size_t>::max)()}; - ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); + if (result_size > 0) { + constexpr uint64_t kMaxSize = + uint64_t{(std::numeric_limits<size_t>::max)()}; + ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); - StringResizeAndOverwrite( - result, static_cast<size_t>(result_size), - [&start, &end, &start_value, sep](char* result_buf, - size_t result_buf_size) { - // Joins strings - result_buf = absl::c_copy(start_value, result_buf); - for (Iterator it = start; ++it != end;) { - result_buf = absl::c_copy(sep, result_buf); - auto&& value = *it; - result_buf = absl::c_copy(value, result_buf); - } - return result_buf_size; - }); + StringResizeAndOverwrite( + result, static_cast<size_t>(result_size), + [&start, &end, &start_value, sep](char* result_buf, + size_t result_buf_size) { + // Joins strings + result_buf = absl::c_copy(start_value, result_buf); + for (Iterator it = start; ++it != end;) { + result_buf = absl::c_copy(sep, result_buf); + auto&& value = *it; + result_buf = absl::c_copy(value, result_buf); + } + return result_buf_size; + }); + } } + return result; } - return result; } // JoinTupleLoop implements a loop over the elements of a std::tuple, which
diff --git a/absl/strings/internal/stringify_sink.cc b/absl/strings/internal/stringify_sink.cc index c9f8258..d70874c 100644 --- a/absl/strings/internal/stringify_sink.cc +++ b/absl/strings/internal/stringify_sink.cc
@@ -14,10 +14,14 @@ #include "absl/strings/internal/stringify_sink.h" +#include <array> #include <cstddef> #include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/strings/numbers.h" #include "absl/strings/string_view.h" +#include "absl/types/source_location.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -29,6 +33,16 @@ buffer_.append(v.data(), v.size()); } +void StringifySourceLocation(StringifySink& sink, SourceLocation l) { + sink.Append(l.file_name()); + std::array<char, 1 + numbers_internal::kFastToBufferSize> buffer; + buffer[0] = ':'; + char* end = numbers_internal::FastIntToBuffer(l.line(), buffer.data() + 1); + const size_t len = static_cast<size_t>(end - buffer.data()); + ABSL_ASSUME(len <= sizeof(buffer)); + sink.Append(absl::string_view(buffer.data(), len)); +} + } // namespace strings_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h index e4e5f9e..c140343 100644 --- a/absl/strings/internal/stringify_sink.h +++ b/absl/strings/internal/stringify_sink.h
@@ -21,7 +21,9 @@ #include <type_traits> #include <utility> +#include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/optimization.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" #include "absl/types/source_location.h" @@ -54,15 +56,24 @@ return sink.buffer_; } +ABSL_ATTRIBUTE_NOINLINE void StringifySourceLocation(StringifySink& sink, + SourceLocation l); + } // namespace strings_internal template <typename Sink> void AbslStringify(Sink& sink, SourceLocation l) { - sink.Append(l.file_name()); - sink.Append(":"); - std::array<char, numbers_internal::kFastToBufferSize> buffer; - numbers_internal::FastIntToBuffer(l.line(), buffer.data()); - sink.Append(buffer.data()); + if constexpr (std::is_same_v<Sink, strings_internal::StringifySink>) { + strings_internal::StringifySourceLocation(sink, l); + } else { + sink.Append(l.file_name()); + std::array<char, 1 + numbers_internal::kFastToBufferSize> buffer; + buffer[0] = ':'; + char* end = numbers_internal::FastIntToBuffer(l.line(), buffer.data() + 1); + const size_t len = static_cast<size_t>(end - buffer.data()); + ABSL_ASSUME(len <= sizeof(buffer)); + sink.Append(absl::string_view(buffer.data(), len)); + } } ABSL_NAMESPACE_END
diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index b79078d..a36136a 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc
@@ -16,6 +16,7 @@ #include <assert.h> +#include <algorithm> #include <cstddef> #include <cstdint> #include <cstring> @@ -25,10 +26,12 @@ #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" #include "absl/base/nullability.h" #include "absl/base/optimization.h" #include "absl/base/throw_delegate.h" #include "absl/strings/internal/append_and_overwrite.h" +#include "absl/strings/numbers.h" #include "absl/strings/resize_and_overwrite.h" #include "absl/strings/string_view.h" @@ -75,8 +78,184 @@ return static_cast<size_t>(sum); } +template <typename Integer> +std::string IntegerToString(Integer i) { + // Any integer (signed/unsigned) up to 64 bits can be formatted into a buffer + // with 22 bytes (including NULL at the end). + constexpr size_t kMaxDigits10 = 22; + std::string result; + StringResizeAndOverwrite( + result, kMaxDigits10, [i](char* start, size_t buf_size) { + // Note: This can be optimized to not write last zero. + char* end = numbers_internal::FastIntToBuffer(i, start); + auto size = static_cast<size_t>(end - start); + ABSL_ASSERT(size < buf_size); + ABSL_ASSUME(size < buf_size); + return size; + }); + return result; +} + +template <typename Float> +std::string FloatToString(Float f) { + std::string result; + StringResizeAndOverwrite(result, numbers_internal::kSixDigitsToBufferSize, + [f](char* start, size_t buf_size) { + size_t size = numbers_internal::SixDigitsToBuffer( + static_cast<double>(f), start); + ABSL_ASSERT(size < buf_size); + ABSL_ASSUME(size < buf_size); + return size; + }); + return result; +} + +template <typename Element> +std::string JoinContiguousImpl(const Element* absl_nullable data, size_t count, + absl::string_view sep) { + std::string result; + if (count != 0 && data != nullptr) { + const Element* absl_nonnull elems = data; + // Use uint64_t to prevent size_t overflow. We assume it is not possible for + // in memory strings to overflow a uint64_t. + uint64_t result_size = elems[0].size(); + for (size_t i = 1; i < count; ++i) { + result_size += sep.size(); + result_size += elems[i].size(); + } + + if (result_size > 0) { + constexpr uint64_t kMaxSize = + uint64_t{(std::numeric_limits<size_t>::max)()}; + ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); + + absl::StringResizeAndOverwrite( + result, static_cast<size_t>(result_size), + [elems, count, sep](char* result_buf, size_t result_buf_size) { + if (!elems[0].empty()) { + memcpy(result_buf, elems[0].data(), elems[0].size()); + result_buf += elems[0].size(); + } + for (size_t i = 1; i < count; ++i) { + if (!sep.empty()) { + memcpy(result_buf, sep.data(), sep.size()); + result_buf += sep.size(); + } + if (!elems[i].empty()) { + memcpy(result_buf, elems[i].data(), elems[i].size()); + result_buf += elems[i].size(); + } + } + return result_buf_size; + }); + } + } + return result; +} + } // namespace +namespace strings_internal { + +absl::string_view FormatHex(Hex hex, char* absl_nonnull end) { + static_assert(numbers_internal::kFastToBufferSize >= 32, + "This function only works when output buffer >= 32 bytes long"); + const size_t real_width = + absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16); + ABSL_ASSUME(real_width <= 16); + if (real_width >= hex.width) { + return absl::string_view(end - real_width, real_width); + } + // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and + // max pad width can be up to 20. + std::memset(end - 32, hex.fill, 16); + // Patch up everything else up to the real_width. + std::memset(end - real_width - 16, hex.fill, 16); + const size_t width = hex.width; + ABSL_ASSUME(width <= numbers_internal::kFastToBufferSize); + return absl::string_view(end - width, width); +} + +absl::string_view FormatDec(Dec dec, char* absl_nonnull end) { + assert(dec.width <= numbers_internal::kFastToBufferSize); + char* const minfill = end - dec.width; + char* writer = end; + uint64_t val = dec.value; + while (val > 9) { + *--writer = static_cast<char>('0' + (val % 10)); + val /= 10; + } + *--writer = static_cast<char>('0' + val); + if (dec.neg) *--writer = '-'; + + ptrdiff_t fillers = writer - minfill; + if (fillers > 0) { + // Tricky: if the fill character is ' ', then it's <fill><+/-><digits> + // But...: if the fill character is '0', then it's <+/-><fill><digits> + bool add_sign_again = false; + if (dec.neg && dec.fill == '0') { // If filling with '0', + ++writer; // ignore the sign we just added + add_sign_again = true; // and re-add the sign later. + } + writer -= fillers; + std::fill_n(writer, fillers, dec.fill); + if (add_sign_again) *--writer = '-'; + } + + const size_t len = static_cast<size_t>(end - writer); + ABSL_ASSUME(len <= numbers_internal::kFastToBufferSize); + return absl::string_view(writer, len); +} + +std::string JoinStringViews(const absl::string_view* absl_nullable data, + size_t count, absl::string_view sep) { + return JoinContiguousImpl(data, count, sep); +} + +std::string JoinStrings(const std::string* absl_nullable data, size_t count, + absl::string_view sep) { + return JoinContiguousImpl(data, count, sep); +} + +std::string SingleArgStrCat(int x) { return IntegerToString(x); } +std::string SingleArgStrCat(unsigned int x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(unsigned long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(long long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(unsigned long long x) { return IntegerToString(x); } +std::string SingleArgStrCat(float x) { return FloatToString(x); } +std::string SingleArgStrCat(double x) { return FloatToString(x); } + +} // namespace strings_internal + +strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> +HighPrecision(float f) { + strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> result; + result.size = + strlen(numbers_internal::RoundTripFloatToBuffer(f, &result.data[0])); + return result; +} + +strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> +HighPrecision(double d) { + strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> result; + result.size = + strlen(numbers_internal::RoundTripDoubleToBuffer(d, &result.data[0])); + return result; +} + +strings_internal::AlphaNumBuffer<numbers_internal::kSixDigitsToBufferSize> +SixDigits(double d) { + strings_internal::AlphaNumBuffer<numbers_internal::kSixDigitsToBufferSize> + result; + result.size = numbers_internal::SixDigitsToBuffer(d, &result.data[0]); + return result; +} + std::string StrCat(const AlphaNum& a, const AlphaNum& b) { std::string result; // Use uint64_t to prevent size_t overflow. We assume it is not possible for
diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 11e3bf6..f749a2e 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h
@@ -111,6 +111,7 @@ #include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/base/port.h" #include "absl/meta/type_traits.h" #include "absl/strings/has_absl_stringify.h" @@ -126,6 +127,9 @@ namespace absl { ABSL_NAMESPACE_BEGIN +struct Hex; +struct Dec; + namespace strings_internal { // AlphaNumBuffer allows a way to pass a string to StrCat without having to do // memory allocation. It is simply a pair of a fixed-size character array, and @@ -136,6 +140,11 @@ size_t size; }; +ABSL_ATTRIBUTE_NOINLINE absl::string_view FormatHex(Hex hex, + char* absl_nonnull end); +ABSL_ATTRIBUTE_NOINLINE absl::string_view FormatDec(Dec dec, + char* absl_nonnull end); + } // namespace strings_internal // Enum that specifies the number of significant digits to return in a `Hex` or @@ -222,23 +231,9 @@ template <typename S> friend void AbslStringify(S& sink, Hex hex) { - static_assert( - numbers_internal::kFastToBufferSize >= 32, - "This function only works when output buffer >= 32 bytes long"); char buffer[numbers_internal::kFastToBufferSize]; - char* const end = &buffer[numbers_internal::kFastToBufferSize]; - auto real_width = - absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16); - if (real_width >= hex.width) { - sink.Append(absl::string_view(end - real_width, real_width)); - } else { - // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and - // max pad width can be up to 20. - std::memset(end - 32, hex.fill, 16); - // Patch up everything else up to the real_width. - std::memset(end - real_width - 16, hex.fill, 16); - sink.Append(absl::string_view(end - hex.width, hex.width)); - } + sink.Append(strings_internal::FormatHex( + hex, &buffer[numbers_internal::kFastToBufferSize])); } private: @@ -277,34 +272,9 @@ template <typename S> friend void AbslStringify(S& sink, Dec dec) { - assert(dec.width <= numbers_internal::kFastToBufferSize); char buffer[numbers_internal::kFastToBufferSize]; - char* const end = &buffer[numbers_internal::kFastToBufferSize]; - char* const minfill = end - dec.width; - char* writer = end; - uint64_t val = dec.value; - while (val > 9) { - *--writer = '0' + (val % 10); - val /= 10; - } - *--writer = '0' + static_cast<char>(val); - if (dec.neg) *--writer = '-'; - - ptrdiff_t fillers = writer - minfill; - if (fillers > 0) { - // Tricky: if the fill character is ' ', then it's <fill><+/-><digits> - // But...: if the fill character is '0', then it's <+/-><fill><digits> - bool add_sign_again = false; - if (dec.neg && dec.fill == '0') { // If filling with '0', - ++writer; // ignore the sign we just added - add_sign_again = true; // and re-add the sign later. - } - writer -= fillers; - std::fill_n(writer, fillers, dec.fill); - if (add_sign_again) *--writer = '-'; - } - - sink.Append(absl::string_view(writer, static_cast<size_t>(end - writer))); + sink.Append(strings_internal::FormatDec( + dec, &buffer[numbers_internal::kFastToBufferSize])); } }; @@ -321,21 +291,13 @@ // HighPrecision is conisderably slower than the default formatting, so only use // it if you need the string to convert back to the same floating-point value. -inline strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> -HighPrecision(float f) { - strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> result; - result.size = - strlen(numbers_internal::RoundTripFloatToBuffer(f, &result.data[0])); - return result; -} +ABSL_ATTRIBUTE_NOINLINE +strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> +HighPrecision(float f); -inline strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> -HighPrecision(double d) { - strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> result; - result.size = - strlen(numbers_internal::RoundTripDoubleToBuffer(d, &result.data[0])); - return result; -} +ABSL_ATTRIBUTE_NOINLINE +strings_internal::AlphaNumBuffer<numbers_internal::kFastToBufferSize> +HighPrecision(double d); // ----------------------------------------------------------------------------- // AlphaNum @@ -357,41 +319,36 @@ AlphaNum(std::initializer_list<T>) = delete; // NOLINT(runtime/explicit) AlphaNum(int x) // NOLINT(runtime/explicit) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(unsigned int x) // NOLINT(runtime/explicit) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(long x) // NOLINT(*) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(unsigned long x) // NOLINT(*) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(long long x) // NOLINT(*) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(unsigned long long x) // NOLINT(*) - : piece_(digits_, static_cast<size_t>( - numbers_internal::FastIntToBuffer(x, digits_) - - &digits_[0])) {} + : piece_( + MakePieceFromEnd(numbers_internal::FastIntToBuffer(x, digits_))) {} AlphaNum(float f) // NOLINT(runtime/explicit) - : piece_(digits_, numbers_internal::SixDigitsToBuffer( - static_cast<double>(f), digits_)) {} + : piece_(MakePieceWithLength(numbers_internal::SixDigitsToBuffer( + static_cast<double>(f), digits_))) {} AlphaNum(double f) // NOLINT(runtime/explicit) - : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} + : piece_(MakePieceWithLength( + numbers_internal::SixDigitsToBuffer(f, digits_))) {} template <size_t size> AlphaNum( // NOLINT(runtime/explicit) const strings_internal::AlphaNumBuffer<size>& buf ABSL_ATTRIBUTE_LIFETIME_BOUND) - : piece_(&buf.data[0], buf.size) {} + : piece_(MakePieceFromBuffer(buf)) {} AlphaNum(const char* absl_nullable c_str // NOLINT(runtime/explicit) ABSL_ATTRIBUTE_LIFETIME_BOUND) @@ -457,6 +414,23 @@ AlphaNum(T e) : AlphaNum(static_cast<bool>(e)) {} // NOLINT(runtime/explicit) private: + absl::string_view MakePieceFromEnd(const char* absl_nonnull end) const { + const size_t len = static_cast<size_t>(end - &digits_[0]); + ABSL_ASSUME(len <= sizeof(digits_)); + return absl::string_view(digits_, len); + } + absl::string_view MakePieceWithLength(size_t len) const { + ABSL_ASSUME(len <= sizeof(digits_)); + return absl::string_view(digits_, len); + } + template <size_t max_size> + static absl::string_view MakePieceFromBuffer( + const strings_internal::AlphaNumBuffer<max_size>& buf) { + const size_t len = buf.size; + ABSL_ASSUME(len <= max_size); + return absl::string_view(&buf.data[0], len); + } + absl::string_view piece_; char digits_[numbers_internal::kFastToBufferSize]; }; @@ -496,58 +470,28 @@ void AppendPieces(std::string* absl_nonnull dest, std::initializer_list<absl::string_view> pieces); -template <typename Integer> -std::string IntegerToString(Integer i) { - // Any integer (signed/unsigned) up to 64 bits can be formatted into a buffer - // with 22 bytes (including NULL at the end). - constexpr size_t kMaxDigits10 = 22; - std::string result; - StringResizeAndOverwrite( - result, kMaxDigits10, [i](char* start, size_t buf_size) { - // Note: This can be optimized to not write last zero. - char* end = numbers_internal::FastIntToBuffer(i, start); - auto size = static_cast<size_t>(end - start); - ABSL_ASSERT(size < buf_size); - return size; - }); - return result; -} - -template <typename Float> -std::string FloatToString(Float f) { - std::string result; - StringResizeAndOverwrite(result, numbers_internal::kSixDigitsToBufferSize, - [f](char* start, size_t buf_size) { - size_t size = numbers_internal::SixDigitsToBuffer( - static_cast<double>(f), start); - ABSL_ASSERT(size < buf_size); - return size; - }); - return result; -} +ABSL_ATTRIBUTE_NOINLINE std::string JoinStringViews( + const absl::string_view* absl_nullable data, size_t count, + absl::string_view sep); +ABSL_ATTRIBUTE_NOINLINE std::string JoinStrings( + const std::string* absl_nullable data, size_t count, absl::string_view sep); // `SingleArgStrCat` overloads take built-in `int`, `long` and `long long` types // (signed / unsigned) to avoid ambiguity on the call side. If we used int32_t // and int64_t, then at least one of the three (`int` / `long` / `long long`) // would have been ambiguous when passed to `SingleArgStrCat`. -inline std::string SingleArgStrCat(int x) { return IntegerToString(x); } -inline std::string SingleArgStrCat(unsigned int x) { - return IntegerToString(x); -} +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(int x); +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(unsigned int x); // NOLINTNEXTLINE -inline std::string SingleArgStrCat(long x) { return IntegerToString(x); } +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(long x); // NOLINTNEXTLINE -inline std::string SingleArgStrCat(unsigned long x) { - return IntegerToString(x); -} +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(unsigned long x); // NOLINTNEXTLINE -inline std::string SingleArgStrCat(long long x) { return IntegerToString(x); } +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(long long x); // NOLINTNEXTLINE -inline std::string SingleArgStrCat(unsigned long long x) { - return IntegerToString(x); -} -inline std::string SingleArgStrCat(float x) { return FloatToString(x); } -inline std::string SingleArgStrCat(double x) { return FloatToString(x); } +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(unsigned long long x); +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(float x); +ABSL_ATTRIBUTE_NOINLINE std::string SingleArgStrCat(double x); // As of September 2023, the SingleArgStrCat() optimization is only enabled for // libc++. The reasons for this are: @@ -648,14 +592,9 @@ // Helper function for the future StrCat default floating-point format, %.6g // This is fast. -inline strings_internal::AlphaNumBuffer< - numbers_internal::kSixDigitsToBufferSize> -SixDigits(double d) { - strings_internal::AlphaNumBuffer<numbers_internal::kSixDigitsToBufferSize> - result; - result.size = numbers_internal::SixDigitsToBuffer(d, &result.data[0]); - return result; -} +ABSL_ATTRIBUTE_NOINLINE +strings_internal::AlphaNumBuffer<numbers_internal::kSixDigitsToBufferSize> +SixDigits(double d); ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/strings/str_replace.cc b/absl/strings/str_replace.cc index 377e30c..0900ae4 100644 --- a/absl/strings/str_replace.cc +++ b/absl/strings/str_replace.cc
@@ -67,6 +67,25 @@ return substitutions; } +std::string ApplySubstitutions(absl::string_view s, + std::vector<ViableSubstitution> subs) { + std::string result; + result.reserve(s.size()); + ApplySubstitutions(s, &subs, &result); + return result; +} + +int ApplySubstitutions(std::vector<ViableSubstitution> subs, + std::string* absl_nonnull target) { + if (subs.empty()) return 0; + + std::string result; + result.reserve(target->size()); + int substitutions = ApplySubstitutions(*target, &subs, &result); + target->swap(result); + return substitutions; +} + } // namespace strings_internal // We can implement this in terms of the generic StrReplaceAll, but
diff --git a/absl/strings/str_replace.h b/absl/strings/str_replace.h index 3af2357..38ef3e4 100644 --- a/absl/strings/str_replace.h +++ b/absl/strings/str_replace.h
@@ -69,7 +69,7 @@ // {"$who", "Bob"}, // {"#Noun", "Apples"}}); // EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); -[[nodiscard]] std::string StrReplaceAll( +[[nodiscard]] ABSL_ATTRIBUTE_NOINLINE std::string StrReplaceAll( absl::string_view s, std::initializer_list<std::pair<absl::string_view, absl::string_view>> replacements); @@ -114,7 +114,7 @@ // {"#Noun", "Apples"}}, &s); // EXPECT_EQ(count, 4); // EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); -int StrReplaceAll( +ABSL_ATTRIBUTE_NOINLINE int StrReplaceAll( std::initializer_list<std::pair<absl::string_view, absl::string_view>> replacements, std::string* absl_nonnull target); @@ -189,34 +189,30 @@ return subs; } -int ApplySubstitutions(absl::string_view s, - std::vector<ViableSubstitution>* absl_nonnull subs_ptr, - std::string* absl_nonnull result_ptr); +ABSL_ATTRIBUTE_NOINLINE int ApplySubstitutions( + absl::string_view s, std::vector<ViableSubstitution>* absl_nonnull subs_ptr, + std::string* absl_nonnull result_ptr); + +ABSL_ATTRIBUTE_NOINLINE std::string ApplySubstitutions( + absl::string_view s, std::vector<ViableSubstitution> subs); + +ABSL_ATTRIBUTE_NOINLINE int ApplySubstitutions( + std::vector<ViableSubstitution> subs, std::string* absl_nonnull target); } // namespace strings_internal template <typename StrToStrMapping> std::string StrReplaceAll(absl::string_view s, const StrToStrMapping& replacements) { - auto subs = strings_internal::FindSubstitutions(s, replacements); - std::string result; - result.reserve(s.size()); - strings_internal::ApplySubstitutions(s, &subs, &result); - return result; + return strings_internal::ApplySubstitutions( + s, strings_internal::FindSubstitutions(s, replacements)); } template <typename StrToStrMapping> int StrReplaceAll(const StrToStrMapping& replacements, std::string* absl_nonnull target) { - auto subs = strings_internal::FindSubstitutions(*target, replacements); - if (subs.empty()) return 0; - - std::string result; - result.reserve(target->size()); - int substitutions = - strings_internal::ApplySubstitutions(*target, &subs, &result); - target->swap(result); - return substitutions; + return strings_internal::ApplySubstitutions( + strings_internal::FindSubstitutions(*target, replacements), target); } ABSL_NAMESPACE_END
diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 244c8a5..dbb50e0 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc
@@ -22,9 +22,11 @@ #include <string> #include <type_traits> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/append_and_overwrite.h" @@ -124,6 +126,14 @@ }); } +ABSL_ATTRIBUTE_NOINLINE std::string SubstituteArray( + absl::string_view format, const absl::string_view* absl_nullable args_array, + size_t num_args) { + std::string result; + SubstituteAndAppendArray(&result, format, args_array, num_args); + return result; +} + Arg::Arg(const void* absl_nullable value) { static_assert(sizeof(scratch_) >= sizeof(value) * 2 + 2, "fix sizeof(scratch_)"); @@ -138,63 +148,19 @@ } while (num != 0); *--ptr = 'x'; *--ptr = '0'; - piece_ = absl::string_view( - ptr, static_cast<size_t>(scratch_ + sizeof(scratch_) - ptr)); + const size_t len = static_cast<size_t>(scratch_ + sizeof(scratch_) - ptr); + ABSL_ASSUME(len <= sizeof(scratch_)); + piece_ = absl::string_view(ptr, len); } } -// TODO(jorg): Don't duplicate so much code between here and str_cat.cc -Arg::Arg(Hex hex) { - char* const end = &scratch_[numbers_internal::kFastToBufferSize]; - char* writer = end; - uint64_t value = hex.value; - do { - *--writer = absl::numbers_internal::kHexChar[value & 0xF]; - value >>= 4; - } while (value != 0); +Arg::Arg(Hex hex) + : piece_(strings_internal::FormatHex( + hex, &scratch_[numbers_internal::kFastToBufferSize])) {} - char* beg; - if (end - writer < hex.width) { - beg = end - hex.width; - std::fill_n(beg, writer - beg, hex.fill); - } else { - beg = writer; - } - - piece_ = absl::string_view(beg, static_cast<size_t>(end - beg)); -} - -// TODO(jorg): Don't duplicate so much code between here and str_cat.cc -Arg::Arg(Dec dec) { - assert(dec.width <= numbers_internal::kFastToBufferSize); - char* const end = &scratch_[numbers_internal::kFastToBufferSize]; - char* const minfill = end - dec.width; - char* writer = end; - uint64_t value = dec.value; - bool neg = dec.neg; - while (value > 9) { - *--writer = '0' + (value % 10); - value /= 10; - } - *--writer = '0' + static_cast<char>(value); - if (neg) *--writer = '-'; - - ptrdiff_t fillers = writer - minfill; - if (fillers > 0) { - // Tricky: if the fill character is ' ', then it's <fill><+/-><digits> - // But...: if the fill character is '0', then it's <+/-><fill><digits> - bool add_sign_again = false; - if (neg && dec.fill == '0') { // If filling with '0', - ++writer; // ignore the sign we just added - add_sign_again = true; // and re-add the sign later. - } - writer -= fillers; - std::fill_n(writer, fillers, dec.fill); - if (add_sign_again) *--writer = '-'; - } - - piece_ = absl::string_view(writer, static_cast<size_t>(end - writer)); -} +Arg::Arg(Dec dec) + : piece_(strings_internal::FormatDec( + dec, &scratch_[numbers_internal::kFastToBufferSize])) {} } // namespace substitute_internal ABSL_NAMESPACE_END
diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 6c62af6..88c5792 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h
@@ -78,9 +78,11 @@ #include <type_traits> #include <vector> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/base/port.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" @@ -131,51 +133,35 @@ piece_ = absl::string_view(scratch_, 1); } Arg(short value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(unsigned short value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(int value) // NOLINT(google-explicit-constructor) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(unsigned int value) // NOLINT(google-explicit-constructor) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(long value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(unsigned long value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(long long value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(unsigned long long value) // NOLINT(*) - : piece_(scratch_, - static_cast<size_t>( - numbers_internal::FastIntToBuffer(value, scratch_) - - scratch_)) {} + : piece_(MakePieceFromEnd( + numbers_internal::FastIntToBuffer(value, scratch_))) {} Arg(float value) // NOLINT(google-explicit-constructor) - : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { - } + : piece_(MakePieceWithLength( + numbers_internal::SixDigitsToBuffer(value, scratch_))) {} Arg(double value) // NOLINT(google-explicit-constructor) - : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { - } + : piece_(MakePieceWithLength( + numbers_internal::SixDigitsToBuffer(value, scratch_))) {} Arg(bool value) // NOLINT(google-explicit-constructor) : piece_(value ? "true" : "false") {} @@ -218,6 +204,16 @@ absl::string_view piece() const { return piece_; } private: + absl::string_view MakePieceFromEnd(const char* absl_nonnull end) const { + const size_t len = static_cast<size_t>(end - scratch_); + ABSL_ASSUME(len <= sizeof(scratch_)); + return absl::string_view(scratch_, len); + } + absl::string_view MakePieceWithLength(size_t len) const { + ABSL_ASSUME(len <= sizeof(scratch_)); + return absl::string_view(scratch_, len); + } + absl::string_view piece_; char scratch_[numbers_internal::kFastToBufferSize]; }; @@ -229,6 +225,10 @@ const absl::string_view* absl_nullable args_array, size_t num_args); +ABSL_ATTRIBUTE_NOINLINE std::string SubstituteArray( + absl::string_view format, const absl::string_view* absl_nullable args_array, + size_t num_args); + #if defined(ABSL_BAD_CALL_IF) constexpr int CalculateOneBit(const char* absl_nonnull format) { // Returns: @@ -543,50 +543,45 @@ // std::string s = absl::Substitute(format, args...); [[nodiscard]] inline std::string Substitute(absl::string_view format) { - std::string result; - SubstituteAndAppend(&result, format); - return result; + return substitute_internal::SubstituteArray(format, nullptr, 0); } [[nodiscard]] inline std::string Substitute( absl::string_view format, const substitute_internal::Arg& a0) { - std::string result; - SubstituteAndAppend(&result, format, a0); - return result; + const absl::string_view args[] = {a0.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece(), a4.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( @@ -594,9 +589,9 @@ const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4, a5); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece(), a4.piece(), a5.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( @@ -604,9 +599,10 @@ const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, const substitute_internal::Arg& a6) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4, a5, a6); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece(), a4.piece(), a5.piece(), + a6.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( @@ -615,9 +611,10 @@ const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, const substitute_internal::Arg& a7) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4, a5, a6, a7); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece(), a4.piece(), a5.piece(), + a6.piece(), a7.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( @@ -626,9 +623,10 @@ const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, const substitute_internal::Arg& a7, const substitute_internal::Arg& a8) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4, a5, a6, a7, a8); - return result; + const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), + a3.piece(), a4.piece(), a5.piece(), + a6.piece(), a7.piece(), a8.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } [[nodiscard]] inline std::string Substitute( @@ -638,9 +636,10 @@ const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, const substitute_internal::Arg& a7, const substitute_internal::Arg& a8, const substitute_internal::Arg& a9) { - std::string result; - SubstituteAndAppend(&result, format, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); - return result; + const absl::string_view args[] = { + a0.piece(), a1.piece(), a2.piece(), a3.piece(), a4.piece(), + a5.piece(), a6.piece(), a7.piece(), a8.piece(), a9.piece()}; + return substitute_internal::SubstituteArray(format, args, std::size(args)); } #if defined(ABSL_BAD_CALL_IF)