Enable string_view assertions
Jonathan Wakely
jwakely.gcc@gmail.com
Wed Mar 14 23:27:00 GMT 2018
On 14 March 2018 at 22:52, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
> (Resending from a different account, sorry for the duplicate).
>
> On 14/03/18 22:12 +0100, François Dumont wrote:
>> constexpr const _CharT&
>> operator[](size_type __pos) const noexcept
>> {
>>- // TODO: Assert to restore in a way compatible with the constexpr.
>>- // __glibcxx_assert(__pos < this->_M_len);
>>+ if (!__builtin_constant_p(__pos))
>>+ __glibcxx_assert(__pos < this->_M_len);
>
> This doesn't do the right thing, because it fails to assert when __pos
> is known at compile-time:
>
> const std::string_view sv;
> sv[100];
>
> This will only assert at -O0. As soon as optimization is enabled the
> value is known inside the function, and __builtin_constant_p(__pos)
> is true, and we don't do the range check.
>
> We also don't do the check for constant expressions, when we should be
> able to give a compilation error, not just ignore it!
>
> Enabling the assertions at -O0 and outside constant expressions is
> slightly better than never enabling them at all, but not good enough
> to remove the "TODO" comments. Ideally we want out-of-range accesses
> to be an error in constant expressions, and fail the runtime assertion
> in non-constant expressions.
>
> But using __builtin_constant_p to do the assertion conditionally
> doesn't do that. In PR 78420 either branch is OK: when the comparison
> is known at compile-time, do it directly, otherwise do it via casting.
> Both give the same result.
>
> Here you do "if the result is not known, check it at runtime,
> otherwise don't check at all".
>
> What we should do instead is declare a new member function like this:
>
> static void
> __out_of_range() __attribute__((__error__("Index out-of-range")));
>
> Then we can refer to that in the cases where (__pos >= _M_len) is
> known at compile-time:
>
> constexpr const _CharT&
> operator[](size_type __pos) const noexcept
> {
> if (__builtin_constant_p(__pos >= _M_len))
> {
> if (__pos >= _M_len)
> __out_of_range();
> }
> else
> __glibcxx_assert(__pos < this->_M_len);
> return *(this->_M_str + __pos);
> }
>
> Now if we try an out-of-range access in a constant expression we get:
>
> sv.cc:4:23: in 'constexpr' expansion of
> 's.std::basic_string_view<char>::operator[](1)'
> /home/jwakely/gcc/8/include/c++/8.0.1/string_view:181:22: error: call
> to non-'constexpr' function 'static void
> std::basic_string_view<_CharT, _Traits>::__out_of_range() [with _CharT
> = char; _Traits = std::char_traits<char>]'
> __out_of_range();
> ~~~~~~~~~~~~~~^~
>
> In a non-constant expression, with optimization it gives a
> compile-time error because of the __error__ attribute:
>
> In member function 'constexpr const _CharT&
> std::basic_string_view<_CharT,
> _Traits>::operator[](std::basic_string_view<_CharT,
> _Traits>::size_type) const [with _CharT = char; _Traits =
> std::char_traits<char>]',
> inlined from 'int main()' at sv.cc:9:6:
> /home/jwakely/gcc/8/include/c++/8.0.1/string_view:181:22: error: call
> to 'std::basic_string_view<char>::__out_of_range' declared with
> attribute error: Index out-of-range
> __out_of_range();
> ~~~~~~~~~~~~~~^~
>
> Finally, without optimization, you can get a run-time failure with
> _GLIBCXX_ASSERTIONS:
>
> /home/jwakely/gcc/8/include/c++/8.0.1/string_view:178: constexpr const
> _CharT& std::basic_string_view<_CharT,
> _Traits>::operator[](std::basic_string_view<_CharT,
> _Traits>::size_type) const [with _CharT = char; _Traits =
> std::char_traits<char>; std::basic_string_view<_CharT,
> _Traits>::size_type = long unsigned int]: Assertion '__pos <
> this->_M_len' failed.
> Aborted (core dumped)
>
> Without _GLIBCXX_ASSERTIONS and without optimization the code compiles
> and runs, with undefined behaviour.
>
> This seems like the optimal set of checks (I've been working on doing
> this in other types too, so we give compile-time errors when we can
> prove the code has a bug).
Here's one way to generalize this idea. We could potentially replace
most of the lightweight __glibcxx_assert checks with this, to get
zero-overhead static checking at compile-time whenever possible (even
in constexpr functions) and have optional run-time assertions for the
remaining cases.
-------------- next part --------------
diff --git a/libstdc++-v3/include/bits/c++config b/libstdc++-v3/include/bits/c++config
index 1eb4679f67c..3342af3a4f0 100644
--- a/libstdc++-v3/include/bits/c++config
+++ b/libstdc++-v3/include/bits/c++config
@@ -462,6 +462,12 @@ namespace std
# define __glibcxx_assert(_Condition)
#endif
+#define __glibcxx_assert2(_Condition, _Action) \
+ do { \
+ if (__builtin_constant_p((_Condition))) { if (!(_Condition)) _Action; \
+ } else { __glibcxx_assert(_Condition); } \
+ } while (false)
+
// Macros for race detectors.
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(A) and
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(A) should be used to explain
diff --git a/libstdc++-v3/include/std/string_view b/libstdc++-v3/include/std/string_view
index 9f39df853e8..a7c74d9d48f 100644
--- a/libstdc++-v3/include/std/string_view
+++ b/libstdc++-v3/include/std/string_view
@@ -169,8 +169,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
constexpr const _CharT&
operator[](size_type __pos) const noexcept
{
- // TODO: Assert to restore in a way compatible with the constexpr.
- // __glibcxx_assert(__pos < this->_M_len);
+ __glibcxx_assert2(__pos < this->_M_len, __undefined());
return *(this->_M_str + __pos);
}
@@ -187,16 +186,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
constexpr const _CharT&
front() const noexcept
{
- // TODO: Assert to restore in a way compatible with the constexpr.
- // __glibcxx_assert(this->_M_len > 0);
+ __glibcxx_assert2(this->_M_len > 0, __undefined());
return *this->_M_str;
}
constexpr const _CharT&
back() const noexcept
{
- // TODO: Assert to restore in a way compatible with the constexpr.
- // __glibcxx_assert(this->_M_len > 0);
+ __glibcxx_assert2(this->_M_len > 0, __undefined());
return *(this->_M_str + this->_M_len - 1);
}
@@ -209,7 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
constexpr void
remove_prefix(size_type __n) noexcept
{
- __glibcxx_assert(this->_M_len >= __n);
+ __glibcxx_assert2(this->_M_len >= __n, __undefined());
this->_M_str += __n;
this->_M_len -= __n;
}
@@ -404,6 +401,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
private:
+ static void
+ __undefined() __attribute__((__error__("undefined behaviour detected")));
static constexpr int
_S_compare(size_type __n1, size_type __n2) noexcept
More information about the Libstdc++
mailing list