-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Prevent UB in DeleteLock() function #18881
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
|
cc @sipa |
maflcko
left a comment
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.
ACK
|
Updated 5a406f7 -> 7e3f40d (pr18881.01 -> pr18881.02, diff):
|
|
Updated 7e3f40d -> a223407 (pr18881.02 -> pr18881.03, diff):
|
jonatack
left a comment
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.
Concept ACK/ACK
|
Updated a223407 -> d3c2686 (pr18881.03 -> pr18881.04, diff):
|
jonatack
left a comment
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.
Iterative re-review (apologies)
|
Updated d3c2686 -> 86ce03c (pr18881.04 -> pr18881.05, diff):
|
jonatack
left a comment
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.
ACK
doc/developer-notes.md
Outdated
|
|
||
| Both cases lead to undefined behavior. | ||
|
|
||
| To prevent the static initialization order problem, be sure to Construct On First Use: |
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 don't think you should make this suggestion in general. This solution, while valuable in rare cases such as logging and tracking locks, is a brute-force one that does not belong in actual application code. It's messy and assumes we don't actually know what's going on (in regard to initialization dependencies) or what are all the edge cases.
You're supposed to expliclty allocate things in a deterministic order during initialization and destroy it in a (usually reversed) deterministic order on shutdown in the appropriate functions in init.cpp, not rely on global initialization order nor first use.
|
ACK on using this solution in this specific place but NACK on suggesting its use in general. |
looked more deeply into this to try to understand it better
I didn't find a direct reference to a "COFU" or "Construct on First Use" idiom in any of these books. The best references online seemed to be:
and I agree with not suggesting it in general. |
|
Updated 86ce03c -> 6bc6868 (pr18881.05 -> pr18881.06, diff):
|
|
ACK 6bc6868 |
jonatack
left a comment
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.
Light code review ACK 6bc6868, this commit appears to be an implementation of the solution in https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use-members
To be exact: https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use |
Yes. |
Why not? Assuming ~LockData had side effects, then not calling the destructor is going to miss those side effects. |
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
|
And if this was somehow "incorrect" then |
|
I'll try to elaborate my opinion :) Line 98 in 068264f
An implicitly-defined destructor, i.e., generated by the compiler, is not the same as a trivial one.
The only difference is that the function-local static In this PR the function-local static If the above arguments are convincing for our community, I agree that making |
The statement has been added to the logger, so I don't see why it shouldn't be added here.
I wasn't aware that this style is used to indicate that #include <iostream>
struct A {
A() { std::cout << __func__ << std::endl; }
~A() { std::cout << __func__ << std::endl; }
};
int main() {
std::cout << "start " << __func__ << std::endl;
static A a;
static A *b = new A();
static A &c = *new A();
std::cout << "end " << __func__ << std::endl;
} |
|
re-ACK 26c093a only change since last review is removing undefined behaviour 🚗 Show signature and timestampSignature: Timestamp of file with hash |
Lines 25 to 26 in b5c423c
That is not the case in so far as the --- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -8,6 +8,8 @@
#include <util/time.h>
#include <mutex>
+#include <type_traits>
+static_assert(std::is_trivially_destructible<BCLog::Logger>::value, "Logger has no trivial ctor.");
const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
and compile with an error: |
| static LockData lockdata; | ||
| return lockdata; | ||
| // This approach guarantees that the object is not destroyed until after its last use. | ||
| // The operating system automatically reclaims all the memory in a program's heap when that program exits. |
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.
| // The operating system automatically reclaims all the memory in a program's heap when that program exits. | |
| // The operating system automatically reclaims all the memory in a program's heap when that program exits. | |
| // Since the destructor is never called, the object and all its members must have an implicit destructor. |
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.
|
Updated 26c093a -> 90eb027 (pr18881.14 -> pr18881.15, diff):
|
|
re-ACK 90eb027, only change is new doc commit 👠 Show signature and timestampSignature: Timestamp of file with hash |
|
Would be nice to get this merged soon, so that I don't have to pull in this branch every time for fuzzing. |
ryanofsky
left a comment
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.
Concept ACK +0.5. The DeleteLock fix makes sense and I really like the code cleanups, but I don't understand the thread_local change.
Code review ACK 90eb027 because all the changes look correct and safe. But I don't know the purpose of commit 26c093a "Replace thread_local g_lockstack with a mutex-protected map (5/6)." It seems like it could have a bad impact on debug performance, and the commit message and PR description don't give a reason for the change.
re: PR Description #18881 (comment)
This PR:
- fixes #18824
adds Construct On First Use idiom documentationRelated to #15233.
Would suggest updating the PR description to:
- Summarize what the UB behavior is so it isn't necessary to read a different github issue
- Give a rationale for replacing the thread_local in the last commit
- Drop the construct idiom text if it's no longer relevant
- Link to #18824 and #15233 at the bottom, and clarify if only the first commit is needed to fix bug #18824, or if the thread_local commit is needed too
|
Several cases of UB are fixed here.
|
Thanks, I definitely think the PR description needs to be improved here and summarize what it is changing and give a clue about how the thread_local change fixes the second problem. If we don't know why the thread local change fixes it, the PR description should say that and not try to be mysterious |
Thank you for review. The PR description has been updated. |
|
re-ACK. Thanks for updating the PR description, this looks good to me. I'm pretty sure there should be a more efficient way to stop referencing the thread_local after it is destroyed (perhaps just resetting the reference in the thread_local destructor) without replacing the thread_local with a map with frequently added & removed entries. But this only affects debugging and probably isn't a big deal. |
|
I am planning to merge this next week unless there are objections or outstanding action items |
👍 and seems fine to merge earlier, too. None of this code is even compiled unless debugging is turned on. |
90eb027 doc: Add and fix comments about never destroyed objects (Hennadii Stepanov) 26c093a Replace thread_local g_lockstack with a mutex-protected map (Hennadii Stepanov) 58e6881 refactor: Refactor duplicated code into LockHeld() (Hennadii Stepanov) f511f61 refactor: Add LockPair type alias (Hennadii Stepanov) 8d8921a refactor: Add LockStackItem type alias (Hennadii Stepanov) 458992b Prevent UB in DeleteLock() function (Hennadii Stepanov) Pull request description: Tracking our instrumented mutexes (`Mutex` and `RecursiveMutex` types) requires that all involved objects should not be destroyed until after their last use. On master (ec79b5f) we have two problems related to the object destroying order: - the function-local `static` `lockdata` object that is destroyed at [program exit](https://en.cppreference.com/w/cpp/utility/program/exit) - the `thread_local` `g_lockstack` that is destroyed at [thread exit](https://en.cppreference.com/w/cpp/language/destructor) Both cases could cause UB at program exit in so far as mutexes are used in other static object destructors. Fix bitcoin#18824 ACKs for top commit: MarcoFalke: re-ACK 90eb027, only change is new doc commit 👠 ryanofsky: Code review ACK 90eb027 because all the changes look correct and safe. But I don't know the purpose of commit 26c093a "Replace thread_local g_lockstack with a mutex-protected map (5/6)." It seems like it could have a bad impact on debug performance, and the commit message and PR description don't give a reason for the change. Tree-SHA512: 99f29157fd1278994e3f6eebccedfd9dae540450f5f8b980518345a89d56b635f943a85b20864cef087027fd0fcdb4880b659ef59bfe5626d110452ae22031c6
Summary: * refactor: Add LockStackItem type alias * refactor: Add LockPair type alias * refactor: Refactor duplicated code into LockHeld() * Replace thread_local g_lockstack with a mutex-protected map This change prevents UB in case of early g_lockstack destroying. Co-authored-by: Wladimir J. van der Laan <laanwj@protonmail.com> * doc: Add and fix comments about never destroyed objects This is a backport of Core [[bitcoin/bitcoin#18881 | PR18881]] Test Plan: ninja all check-all Also in debug mode. Reviewers: #bitcoin_abc, majcosta Reviewed By: #bitcoin_abc, majcosta Subscribers: majcosta Differential Revision: https://reviews.bitcoinabc.org/D7525
Tracking our instrumented mutexes (
MutexandRecursiveMutextypes) requires that all involved objects should not be destroyed until after their last use. On master (ec79b5f) we have two problems related to the object destroying order:staticlockdataobject that is destroyed at program exitthread_localg_lockstackthat is destroyed at thread exitBoth cases could cause UB at program exit in so far as mutexes are used in other static object destructors.
Fix #18824