-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[libc++] Add internal checks for some basic_streambuf invariants #144602
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
Conversation
@llvm/pr-subscribers-libcxx Author: Louis Dionne (ldionne) ChangesThese invariants are always expected to hold, however it's not always clear that they do. Adding explicit checks for these invariants inside non-trivial functions of basic_streambuf makes that clear. Full diff: https://github.com/llvm/llvm-project/pull/144602.diff 1 Files Affected:
diff --git a/libcxx/include/streambuf b/libcxx/include/streambuf
index 585ae7af65aa8..7dc4e31cc2324 100644
--- a/libcxx/include/streambuf
+++ b/libcxx/include/streambuf
@@ -119,6 +119,7 @@ protected:
# include <__locale>
# include <__type_traits/is_same.h>
# include <__utility/is_valid_range.h>
+# include <__utility/scope_guard.h>
# include <climits>
# include <ios>
# include <iosfwd>
@@ -178,18 +179,27 @@ public:
// Get and put areas:
// 27.6.2.2.3 Get area:
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 streamsize in_avail() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (gptr() < egptr())
return static_cast<streamsize>(egptr() - gptr());
return showmanyc();
}
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type snextc() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (sbumpc() == traits_type::eof())
return traits_type::eof();
return sgetc();
}
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type sbumpc() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (gptr() == egptr())
return uflow();
int_type __c = traits_type::to_int_type(*gptr());
@@ -198,6 +208,9 @@ public:
}
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type sgetc() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (gptr() == egptr())
return underflow();
return traits_type::to_int_type(*gptr());
@@ -207,6 +220,9 @@ public:
// 27.6.2.2.4 Putback:
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type sputbackc(char_type __c) {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (eback() == gptr() || !traits_type::eq(__c, *(gptr() - 1)))
return pbackfail(traits_type::to_int_type(__c));
this->gbump(-1);
@@ -214,6 +230,9 @@ public:
}
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type sungetc() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (eback() == gptr())
return pbackfail();
this->gbump(-1);
@@ -222,6 +241,9 @@ public:
// 27.6.2.2.5 Put area:
inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 int_type sputc(char_type __c) {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (pptr() == epptr())
return overflow(traits_type::to_int_type(__c));
*pptr() = __c;
@@ -317,6 +339,9 @@ protected:
virtual streamsize showmanyc() { return 0; }
virtual streamsize xsgetn(char_type* __s, streamsize __n) {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
int_type __c;
streamsize __i = 0;
while (__i < __n) {
@@ -338,6 +363,9 @@ protected:
virtual int_type underflow() { return traits_type::eof(); }
virtual int_type uflow() {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
if (underflow() == traits_type::eof())
return traits_type::eof();
int_type __c = traits_type::to_int_type(*gptr());
@@ -350,6 +378,9 @@ protected:
// 27.6.2.4.5 Put area:
virtual streamsize xsputn(const char_type* __s, streamsize __n) {
+ __check_invariants();
+ auto __guard = std::__make_scope_guard([this] { this->__check_invariants(); });
+
streamsize __i = 0;
while (__i < __n) {
if (pptr() >= epptr()) {
@@ -370,6 +401,15 @@ protected:
virtual int_type overflow(int_type = traits_type::eof()) { return traits_type::eof(); }
+ // This function checks some invariants of the class (it isn't exhaustive).
+ _LIBCPP_HIDE_FROM_ABI void __check_invariants() const {
+ _LIBCPP_ASSERT_INTERNAL(pbase() <= pptr(), "this is an invariant of the class");
+ _LIBCPP_ASSERT_INTERNAL(pptr() <= epptr(), "this is an invariant of the class");
+
+ _LIBCPP_ASSERT_INTERNAL(eback() <= gptr(), "this is an invariant of the class");
+ _LIBCPP_ASSERT_INTERNAL(gptr() <= egptr(), "this is an invariant of the class");
+ }
+
private:
locale __loc_;
char_type* __binp_ = nullptr;
|
I'm a bit conflicted: on the one hand these invariant checks are extremely useful (especially when debugging tricky issues), but on the other hand they're somewhat clunky. We could also technically do this in all methods of all classes in the library, and clearly we don't want to go down that route. @philnik777 Do you have thoughts on this? I could also scale it down to just the two methods where checking these invariants is really helpful to debug the issue I'm looking into. |
2f49318
to
68b73da
Compare
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 guess this LGTM. I'd much rather have some other way to say "this is an invariant", but I don't know how we'd do it.
These invariants are always expected to hold, however it's not always clear that they do. Adding explicit checks for these invariants inside non-trivial functions of basic_streambuf makes that clear.
68b73da
to
83e20ab
Compare
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.
Seems like that we should merge this now. CI failures are probably unrelated.
Sorry, this went under my radar. Merging. |
This unfortunately broke the LLDB macOS CI:
Same issue as #149477 We worked around it last time by not using a lambda in #149390. But that doesn't seem like a reasonable ask in this case? I'll try to repro this locally and see if there's an LLDB fix for this |
@ldionne could we revert this for now until I fix/skip the tests? I know what the root cause is, just working towards a fix. But won't be landing it until monday. If I end up not fixing it by then we can skip the tests temporarily to unblock libc++ Also, curious why these tests didn't run in pre-merge CI. Need to check EDIT: looks like we skip these tests on Linux :( |
Actually nvm, I'm working around it in: #154962 No need to revert |
This patch works around an assertion that we hit in the `LambdaExpr` constructor when we call it from `ASTNodeImporter::VisitLambdaExpr` (see #149477). The lambda that we imported doesn't have the `NumCaptures` field accurately set to the one on the source decl. This is because in `MinimalImport` mode, we skip importing of lambda definitions: https://github.com/llvm/llvm-project/blob/e21b0dd81928a3266df0e3ede008fb7a6676ff95/clang/lib/AST/ASTImporter.cpp#L2499 In practice we have seen this assertion occur in our `import-std-module` test-suite when libc++ headers decide to use lambdas inside inline function bodies (the latest failure being caused by #144602). To avoid running into this whenever libc++ decides to use lambdas in headers, this patch skips `ASTImport` of lambdas alltogether. Ideally this would bubble up to the user or log as an error, but we swallow the `ASTImportError`s currently. The only way this codepath is hit is when lambdas are used inside functions in defined in the expression evaluator, or when importing AST nodes from Clang modules. Both of these are very niche use-cases (for now), so a workaround seemed appropriate.
…(#154962) This patch works around an assertion that we hit in the `LambdaExpr` constructor when we call it from `ASTNodeImporter::VisitLambdaExpr` (see llvm/llvm-project#149477). The lambda that we imported doesn't have the `NumCaptures` field accurately set to the one on the source decl. This is because in `MinimalImport` mode, we skip importing of lambda definitions: https://github.com/llvm/llvm-project/blob/e21b0dd81928a3266df0e3ede008fb7a6676ff95/clang/lib/AST/ASTImporter.cpp#L2499 In practice we have seen this assertion occur in our `import-std-module` test-suite when libc++ headers decide to use lambdas inside inline function bodies (the latest failure being caused by llvm/llvm-project#144602). To avoid running into this whenever libc++ decides to use lambdas in headers, this patch skips `ASTImport` of lambdas alltogether. Ideally this would bubble up to the user or log as an error, but we swallow the `ASTImportError`s currently. The only way this codepath is hit is when lambdas are used inside functions in defined in the expression evaluator, or when importing AST nodes from Clang modules. Both of these are very niche use-cases (for now), so a workaround seemed appropriate.
These invariants are always expected to hold, however it's not always clear that they do. Adding explicit checks for these invariants inside non-trivial functions of basic_streambuf makes that clear.