From 08e2614810949e24231f123dda79bfbdcb30fea8 Mon Sep 17 00:00:00 2001 From: Eric Niebler <eniebler@fb.com> Date: Thu, 3 Sep 2020 16:38:10 -0700 Subject: [PATCH] Avoid use of string functions in constexpr context when the compiler can't support that Summary: It's insufficient to infer support for `constexpr` `std::strlen` by merely testing that we're using libstdc++ on a compiler other than clang. Some other compilers besides gcc can be using libstdc++ that _don't_ have `constexpr` `std::strlen`. Rather, use the compiler itself to detect support for either (a) the builtins, or (b) a `constexpr` implementation of the stdlb function, falling back to a custom `constexpr` implementation otherwise. Also, handle MSVC, which supports `__builtin_strlen` but which doesn't support either `__has_feature` or `__has_builtin`. Give `constexpr_strcmp` the same handling, but leave MSVC out of the fun because it doesn't provide `__builtin_strcmp`. Also, remove the unnecessary include of `<type_traits>`. Finally, make these functions `noexcept` for good measure. Extra info: The overload sets in the `detail` namespace in this diff are a little magical. The first overload only compiles if (a) the string literal `"a"` can be `static_cast` to `const Char*` (that is, only if `Char` is `char`), and (b) if `FOLLY_DETAIL_STR???` yields a compile-time constant (so it can be used to initialize a non-type template parameter). If both of those things are true, that overload is preferred because the last argument requires no conversion. If either of those two conditions is false, that overload has a substitution failure and is removed from consideration. Then the second overload is selected. Reviewed By: yfeldblum Differential Revision: D23407997 fbshipit-source-id: f5838c578cb62b8fd777052223222882a6575429 --- folly/FixedString.h | 2 +- folly/portability/Constexpr.h | 89 ++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/folly/FixedString.h b/folly/FixedString.h index f0f535469..c69571ec8 100644 --- a/folly/FixedString.h +++ b/folly/FixedString.h @@ -98,7 +98,7 @@ constexpr const Char (&checkNullTerminated(const Char (&a)[N]) noexcept)[N] { return a[N - 1u] == Char(0) #ifndef NDEBUG // In Debug mode, guard against embedded nulls: - && N - 1u == folly::detail::constexpr_strlen_fallback(a) + && N - 1u == folly::constexpr_strlen(a) #endif ? decltype(a)(a) : (assertNotNullTerminated(), decltype(a)(a)); diff --git a/folly/portability/Constexpr.h b/folly/portability/Constexpr.h index 1b31a0292..996965c63 100644 --- a/folly/portability/Constexpr.h +++ b/folly/portability/Constexpr.h @@ -20,68 +20,91 @@ #include <cstdint> #include <cstring> -#include <type_traits> namespace folly { namespace detail { +#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) || \ + FOLLY_HAS_BUILTIN(__builtin_strlen) || defined(_MSC_VER) +#define FOLLY_DETAIL_STRLEN __builtin_strlen +#else +#define FOLLY_DETAIL_STRLEN ::std::strlen +#endif + +#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) || \ + FOLLY_HAS_BUILTIN(__builtin_strcmp) +#define FOLLY_DETAIL_STRCMP __builtin_strcmp +#else +#define FOLLY_DETAIL_STRCMP ::std::strcmp +#endif + +// This overload is preferred if Char is char and if FOLLY_DETAIL_STRLEN +// yields a compile-time constant. +template < + typename Char, + size_t = FOLLY_DETAIL_STRLEN(static_cast<const Char*>(""))> +constexpr std::size_t constexpr_strlen_internal(const Char* s, int) noexcept { + return FOLLY_DETAIL_STRLEN(s); +} template <typename Char> -constexpr size_t constexpr_strlen_fallback(const Char* s) { - size_t ret = 0; +constexpr std::size_t constexpr_strlen_internal( + const Char* s, + std::size_t ret) noexcept { while (*s++) { ++ret; } return ret; } +template <typename Char> +constexpr size_t constexpr_strlen_fallback(const Char* s) noexcept { + return constexpr_strlen_internal(s, (std::size_t)0); +} + static_assert( constexpr_strlen_fallback("123456789") == 9, "Someone appears to have broken constexpr_strlen..."); +// This overload is preferred if Char is char and if FOLLY_DETAIL_STRCMP +// yields a compile-time constant. +template < + typename Char, + int = FOLLY_DETAIL_STRCMP(static_cast<const Char*>(""), "")> +constexpr int +constexpr_strcmp_internal(const Char* s1, const Char* s2, int) noexcept { + return FOLLY_DETAIL_STRCMP(s1, s2); +} template <typename Char> -constexpr int constexpr_strcmp_fallback(const Char* s1, const Char* s2) { +constexpr int constexpr_strcmp_internal( + const Char* s1, + const Char* s2, + std::size_t) noexcept { while (*s1 && *s1 == *s2) { ++s1, ++s2; } return int(*s2 < *s1) - int(*s1 < *s2); } -} // namespace detail - template <typename Char> -constexpr size_t constexpr_strlen(const Char* s) { - return detail::constexpr_strlen_fallback(s); +constexpr int constexpr_strcmp_fallback( + const Char* s1, + const Char* s2) noexcept { + return constexpr_strcmp_internal(s1, s2, (std::size_t)0); } -template <> -constexpr size_t constexpr_strlen(const char* s) { -#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) - // clang provides a constexpr builtin - return __builtin_strlen(s); -#elif defined(__GLIBCXX__) && !defined(__clang__) - // strlen() happens to already be constexpr under gcc - return std::strlen(s); -#else - return detail::constexpr_strlen_fallback(s); -#endif -} +#undef FOLLY_DETAIL_STRCMP +#undef FOLLY_DETAIL_STRLEN + +} // namespace detail template <typename Char> -constexpr int constexpr_strcmp(const Char* s1, const Char* s2) { - return detail::constexpr_strcmp_fallback(s1, s2); +constexpr size_t constexpr_strlen(const Char* s) noexcept { + return ::folly::detail::constexpr_strlen_internal(s, 0); } -template <> -constexpr int constexpr_strcmp(const char* s1, const char* s2) { -#if FOLLY_HAS_FEATURE(cxx_constexpr_string_builtins) - // clang provides a constexpr builtin - return __builtin_strcmp(s1, s2); -#elif defined(__GLIBCXX__) && !defined(__clang__) - // strcmp() happens to already be constexpr under gcc - return std::strcmp(s1, s2); -#else - return detail::constexpr_strcmp_fallback(s1, s2); -#endif +template <typename Char> +constexpr int constexpr_strcmp(const Char* s1, const Char* s2) noexcept { + return ::folly::detail::constexpr_strcmp_internal(s1, s2, 0); } } // namespace folly -- 2.26.2