-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Implement LWG4023 #87513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc++] Implement LWG4023 #87513
Conversation
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesThis patch implements LWG4023 by adding explicit assertions for the added preconditions and also fixes a few tests that were violating these preconditions. Full diff: https://github.com/llvm/llvm-project/pull/87513.diff 7 Files Affected:
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index f471c430b19c18..bf64dfdee963d9 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -49,7 +49,7 @@
"`4012 <https://wg21.link/LWG4012>`__","``common_view::begin/end`` are missing the ``simple-view`` check","Tokyo March 2024","","","|ranges|"
"`4013 <https://wg21.link/LWG4013>`__","``lazy_split_view::outer-iterator::value_type`` should not provide default constructor","Tokyo March 2024","","","|ranges|"
"`4016 <https://wg21.link/LWG4016>`__","container-insertable checks do not match what container-inserter does","Tokyo March 2024","","",""
-"`4023 <https://wg21.link/LWG4023>`__","Preconditions of ``std::basic_streambuf::setg/setp``","Tokyo March 2024","","",""
+"`4023 <https://wg21.link/LWG4023>`__","Preconditions of ``std::basic_streambuf::setg/setp``","Tokyo March 2024","|Complete|","19.0",""
"`4025 <https://wg21.link/LWG4025>`__","Move assignment operator of ``std::expected<cv void, E>`` should not be conditionally deleted","Tokyo March 2024","","",""
"`4030 <https://wg21.link/LWG4030>`__","Clarify whether arithmetic expressions in ``[numeric.sat.func]`` are mathematical or C++","Tokyo March 2024","","",""
"`4031 <https://wg21.link/LWG4031>`__","``bad_expected_access<void>`` member functions should be ``noexcept``","Tokyo March 2024","","",""
diff --git a/libcxx/include/streambuf b/libcxx/include/streambuf
index aec537866c2031..a4d6d22c7d2121 100644
--- a/libcxx/include/streambuf
+++ b/libcxx/include/streambuf
@@ -107,6 +107,7 @@ protected:
*/
+#include <__assert>
#include <__config>
#include <__fwd/streambuf.h>
#include <__type_traits/is_same.h>
@@ -233,6 +234,9 @@ protected:
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void gbump(int __n) { __ninp_ += __n; }
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void setg(char_type* __gbeg, char_type* __gnext, char_type* __gend) {
+ _LIBCPP_ASSERT_VALID_INPUT_RANGE(__gbeg <= __gnext, "[gbeg, gnext) must be a valid range");
+ _LIBCPP_ASSERT_VALID_INPUT_RANGE(__gbeg <= __gend, "[gbeg, gend) must be a valid range");
+ _LIBCPP_ASSERT_VALID_INPUT_RANGE(__gnext <= __gend, "[gnext, gend) must be a valid range");
__binp_ = __gbeg;
__ninp_ = __gnext;
__einp_ = __gend;
@@ -248,6 +252,7 @@ protected:
_LIBCPP_HIDE_FROM_ABI void __pbump(streamsize __n) { __nout_ += __n; }
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void setp(char_type* __pbeg, char_type* __pend) {
+ _LIBCPP_ASSERT_VALID_INPUT_RANGE(__pbeg <= __pend, "[pbeg, pend) must be a valid range");
__bout_ = __nout_ = __pbeg;
__eout_ = __pend;
}
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
index 58067511950703..b458f93601a1ba 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
@@ -57,18 +57,20 @@ int main(int, char**)
test<char> t2 = t;
}
{
- char g1, g2, g3, p1, p3;
+ char g[3];
+ char p[3];
test<char> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<char> t2 = t;
}
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
{
- wchar_t g1, g2, g3, p1, p3;
+ wchar_t g[3];
+ wchar_t p[3];
test<wchar_t> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<wchar_t> t2 = t;
}
{
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
index 8a976e77f0f13f..45a8cdf3a23fea 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
@@ -59,10 +59,11 @@ int main(int, char**)
t2 = t;
}
{
- char g1, g2, g3, p1, p3;
+ char g[3];
+ char p[3];
test<char> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<char> t2;
t2 = t;
}
@@ -73,10 +74,11 @@ int main(int, char**)
t2 = t;
}
{
- wchar_t g1, g2, g3, p1, p3;
+ wchar_t g[3];
+ wchar_t p[3];
test<wchar_t> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<wchar_t> t2;
t2 = t;
}
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
index c575c2cb12711a..b90c4c053c9155 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
@@ -68,10 +68,11 @@ int main(int, char**)
t2.swap(t);
}
{
- char g1, g2, g3, p1, p3;
+ char g[3];
+ char p[3];
test<char> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<char> t2;
t2.swap(t);
}
@@ -82,10 +83,11 @@ int main(int, char**)
t2.swap(t);
}
{
- wchar_t g1, g2, g3, p1, p3;
+ wchar_t g[3];
+ wchar_t p[3];
test<wchar_t> t;
- t.setg(&g1, &g2, &g3);
- t.setp(&p1, &p3);
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
test<wchar_t> t2;
t2.swap(t);
}
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.get.area/setg.assert.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.get.area/setg.assert.pass.cpp
new file mode 100644
index 00000000000000..becf89b12fdd18
--- /dev/null
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.get.area/setg.assert.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <streambuf>
+
+// template <class charT, class traits = char_traits<charT> >
+// class basic_streambuf;
+
+// void setg(char_type* gbeg, char_type* gnext, char_type* gend);
+
+#include <algorithm>
+#include <iterator>
+#include <streambuf>
+#include <string>
+
+#include "check_assertion.h"
+#include "make_string.h"
+#include "test_macros.h"
+
+template <class CharT>
+struct streambuf : public std::basic_streambuf<CharT> {
+ typedef std::basic_streambuf<CharT> base;
+
+ streambuf() {}
+
+ void setg(CharT* gbeg, CharT* gnext, CharT* gend) { base::setg(gbeg, gnext, gend); }
+};
+
+template <class CharT>
+void test() {
+ std::basic_string<CharT> str = MAKE_STRING(CharT, "ABCDEF");
+ CharT arr[6];
+ std::copy(str.begin(), str.end(), arr);
+
+ {
+ streambuf<CharT> buff;
+ TEST_LIBCPP_ASSERT_FAILURE(
+ buff.setg(std::begin(arr) + 1, std::begin(arr), std::end(arr)), "[gbeg, gnext) must be a valid range");
+ }
+ {
+ streambuf<CharT> buff;
+ TEST_LIBCPP_ASSERT_FAILURE(
+ buff.setg(std::begin(arr) + 1, std::begin(arr) + 1, std::begin(arr)), "[gbeg, gend) must be a valid range");
+ }
+ {
+ streambuf<CharT> buff;
+ TEST_LIBCPP_ASSERT_FAILURE(
+ buff.setg(std::begin(arr), std::begin(arr) + 3, std::begin(arr) + 2), "[gnext, gend) must be a valid range");
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.put.area/setp.assert.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.put.area/setp.assert.pass.cpp
new file mode 100644
index 00000000000000..abd42272de508c
--- /dev/null
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.put.area/setp.assert.pass.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <streambuf>
+
+// template <class charT, class traits = char_traits<charT> >
+// class basic_streambuf;
+
+// void setp(char_type* pbeg, char_type* pend);
+
+#include <algorithm>
+#include <iterator>
+#include <streambuf>
+#include <string>
+
+#include "check_assertion.h"
+#include "make_string.h"
+#include "test_macros.h"
+
+template <class CharT>
+struct streambuf : public std::basic_streambuf<CharT> {
+ typedef std::basic_streambuf<CharT> base;
+
+ streambuf() {}
+
+ void setp(CharT* pbeg, CharT* pend) { base::setp(pbeg, pend); }
+};
+
+template <class CharT>
+void test() {
+ std::basic_string<CharT> str = MAKE_STRING(CharT, "ABCDEF");
+ CharT arr[6];
+ std::copy(str.begin(), str.end(), arr);
+
+ {
+ streambuf<CharT> buff;
+ TEST_LIBCPP_ASSERT_FAILURE(buff.setp(std::begin(arr) + 3, std::begin(arr)), "[pbeg, pend) must be a valid range");
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
|
You can test this locally with the following command:git-clang-format --diff a06073f91e7bbbb532e68bbf6b903c2f5051f4c2 cd5df7e3a34289a1a99cbb2ad0aeea39df27f8f6 -- libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.get.area/setg.assert.pass.cpp libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.put.area/setp.assert.pass.cpp libcxx/include/streambuf libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp View the diff from clang-format here.diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
index b458f93601..1796c47c16 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.cons/copy.pass.cpp
@@ -57,21 +57,21 @@ int main(int, char**)
test<char> t2 = t;
}
{
- char g[3];
- char p[3];
- test<char> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<char> t2 = t;
+ char g[3];
+ char p[3];
+ test<char> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<char> t2 = t;
}
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
{
- wchar_t g[3];
- wchar_t p[3];
- test<wchar_t> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<wchar_t> t2 = t;
+ wchar_t g[3];
+ wchar_t p[3];
+ test<wchar_t> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<wchar_t> t2 = t;
}
{
test<wchar_t> t;
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
index 45a8cdf3a2..b7e5a210c9 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/assign.pass.cpp
@@ -59,13 +59,13 @@ int main(int, char**)
t2 = t;
}
{
- char g[3];
- char p[3];
- test<char> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<char> t2;
- t2 = t;
+ char g[3];
+ char p[3];
+ test<char> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<char> t2;
+ t2 = t;
}
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
{
@@ -74,13 +74,13 @@ int main(int, char**)
t2 = t;
}
{
- wchar_t g[3];
- wchar_t p[3];
- test<wchar_t> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<wchar_t> t2;
- t2 = t;
+ wchar_t g[3];
+ wchar_t p[3];
+ test<wchar_t> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<wchar_t> t2;
+ t2 = t;
}
#endif
std::locale::global(std::locale(LOCALE_en_US_UTF_8));
diff --git a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
index b90c4c053c..b0f5e6f741 100644
--- a/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
+++ b/libcxx/test/std/input.output/stream.buffers/streambuf/streambuf.protected/streambuf.assign/swap.pass.cpp
@@ -68,13 +68,13 @@ int main(int, char**)
t2.swap(t);
}
{
- char g[3];
- char p[3];
- test<char> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<char> t2;
- t2.swap(t);
+ char g[3];
+ char p[3];
+ test<char> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<char> t2;
+ t2.swap(t);
}
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
{
@@ -83,13 +83,13 @@ int main(int, char**)
t2.swap(t);
}
{
- wchar_t g[3];
- wchar_t p[3];
- test<wchar_t> t;
- t.setg(&g[0], &g[1], &g[2]);
- t.setp(&p[0], &p[2]);
- test<wchar_t> t2;
- t2.swap(t);
+ wchar_t g[3];
+ wchar_t p[3];
+ test<wchar_t> t;
+ t.setg(&g[0], &g[1], &g[2]);
+ t.setp(&p[0], &p[2]);
+ test<wchar_t> t2;
+ t2.swap(t);
}
#endif
std::locale::global(std::locale(LOCALE_en_US_UTF_8));
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
libcxx/include/streambuf
Outdated
@@ -233,6 +234,9 @@ protected: | |||
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void gbump(int __n) { __ninp_ += __n; } | |||
|
|||
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void setg(char_type* __gbeg, char_type* __gnext, char_type* __gend) { | |||
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__gbeg <= __gnext, "[gbeg, gnext) must be a valid range"); | |||
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__gbeg <= __gend, "[gbeg, gend) must be a valid range"); | |||
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__gnext <= __gend, "[gnext, gend) must be a valid range"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to this patch, but is this the best way to test this?
If it's not a valid range we trigger UB. Would it be better to test the value cased to an uintptr_t
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually a great point. We might want something that looks like https://github.com/llvm/llvm-project/blob/main/libcxx/include/__utility/is_pointer_in_range.h#L35.
Maybe __is_valid_range(_Tp* __first, _Tp* __last)
?
@philnik777 probably has thoughts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought about adding something similar. If we wanted to we could check with __asan_region_is_poisoned
that inputs to algorithms (or here) are in fact valid memory. That would probably reduce the amount of not-actually-valid uses of algorithms in the wild quite a bit. e.g. with the mismatch
vectorization we found a bug in clang-tidy because mismatch
got a second range that was too small. It would also give a somewhat better diagnostic message to people misusing algorithms. Maybe something like __asan_assert_unpoisoned_region
would be best, so people still get the nice ASan output, just with a slightly different failure message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This patch implements LWG4023 by adding explicit assertions for the added preconditions and also fixes a few tests that were violating these preconditions.
dca9749
to
cd5df7e
Compare
This patch implements LWG4023 by adding explicit assertions for the added preconditions and also fixes a few tests that were violating these preconditions.