-
Notifications
You must be signed in to change notification settings - Fork 38.6k
util: Fix Racy ParseOpCode function initialization #22875
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
|
Wouldn't it be simpler to do something like: class MapOpNames : public std::map<std::string, opcodetype>
{
public:
MapOpNames()
{
for (unsigned int op = 0; op <= MAX_OPCODE; op++) {
...
}
}
};
opcodetype ParseOpCode(const std::string& s)
{
const static MapOpNames mapOpNames;
auto it = mapOpNames.find(s);
...
}? |
|
maybe just move map initialization out of ParseOpCode? static std::map<std::string, opcodetype> get_map()
{
std::map<std::string, opcodetype> map;
for (unsigned int op = 0; op <= MAX_OPCODE; op++) {
...
}
return map;
};
opcodetype ParseOpCode(const std::string& s)
{
const static std::map<std::string, opcodetype> mapOpNames(get_map());
auto it = mapOpNames.find(s);
...
} |
|
Those are both solid suggestions, I guess I wasn't familiar enough with initializer rules to see those would be safe. Will probably go with @kirillkovalenko's approach |
|
Although thinking about it it's not clear to me that in your version get_map isn't called every time the function is run? |
|
Why would it? You can test yourself: #include <iostream>
static int get_map() {
std::cout << __func__ << std::endl;
return 42;
};
bool ParseOpCode() {
const static auto mapOpNames{get_map()};
return mapOpNames == 1;
}
int main() {
ParseOpCode();
ParseOpCode();
} |
It's the way language/runtime implements it, basically a guarantee |
|
It doesn't seem to me clear that the behavior is specified (although
certainly it seems to be defined) to be not equivalent to something like
this:
```c++
static int v = f();
int&& x = f();
static int v = x;
```
Whereby f() is always invoked since v is still only initialized once in
either case.
AJ's version does not have this ambiguity (but I'd likely rather make it
composition than inheritance as inherit from std is generally a smell).
|
|
There are issues in other parts of the codebase if this assumption doesn't hold |
|
reading the spec more closely I think it should be ok |
|
I think you could also do: namespace {
class OpCodeParser
{
private:
std::map<std::string, opcodetype> mapOpNames;
public:
OpCodeParser()
{
for (unsigned int op = 0; op <= MAX_OPCODE; op++) {
...
}
}
opcodetype Parse(const std::string& s) const {
auto it = mapOpNames.find(s);
...
}
};
} // namespace
opcodetype ParseOpCode(const std::string& s)
{
const static OpCodeParser ocp;
return ocp.Parse(s);
} |
3891795 to
6de81ee
Compare
|
went with @ajtowns suggested edit. |
6de81ee to
d5e006c
Compare
src/core_read.cpp
Outdated
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.
nit: according to clang-format:
diff --git a/src/core_read.cpp b/src/core_read.cpp
index bd29e2073e..ba535f745f 100644
--- a/src/core_read.cpp
+++ b/src/core_read.cpp
@@ -45,7 +45,8 @@ public:
}
}
}
- opcodetype Parse(const std::string& s) const {
+ opcodetype Parse(const std::string& s) const
+ {
auto it = mapOpNames.find(s);
if (it == mapOpNames.end()) throw std::runtime_error("script parse error: unknown opcode");
return it->second;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.
Nit coding-style: opening brace for functions/classes on a separate line.
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
Github-Pull: bitcoin#22875 Rebased-From: d5e006c84a12b194a27e4eb0c620da8fbc113c18
|
Are you still working on this? If not, I suggest to close this PR |
bitcoin-tx is single threaded. I don't think tooling exists to catch "races" in single-threaded programs, whatever that means. |
|
well i think core_read is still included in libbitcoin sources so i think it should be threadsafe -- good rule of thumb is that any pure function should be threadsafe, always. i don't think closing the PR is a good idea; i'll try to rebase it when i have time. |
There's some risk that this function gets moved to an utility at some point when it is needed somewhere else, and people forget to make it thread-safe. I'm fine with making this change, but please address the comments. |
Github-Pull: bitcoin#22875 Rebased-From: d5e006c84a12b194a27e4eb0c620da8fbc113c18
d5e006c to
07aa5f7
Compare
07aa5f7 to
cfa2b8d
Compare
|
rebased, nits addressed |
sipa
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.
Code review ACK cfa2b8dbf9fc6d020281e6521e83853838f62f81
src/core_read.cpp
Outdated
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.
Nit coding-style: opening brace for functions/classes on a separate line.
|
review ACK cfa2b8dbf9fc6d020281e6521e83853838f62f81 🗡 Show signatureSignature: |
|
@JeremyRubin would be really nice to fix the two nits, but this can be also be merged as-is. Please let us know what you prefer. |
|
since the current PR has 2 code review ACKs on cfa2b8d, i don't want to burden anyone with re-review on that hash, so I pushed a fixup with it. If you want i can squash it, or you can on merge. |
|
Thank you for the fixup. Please squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits |
|
maintainers aren't allowed to squash, as it would break the signature check script |
83cd1a9 to
7b481f0
Compare
|
squashed |
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.
re-ACK 7b481f0 🗣
Show signature
Signature:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
re-ACK 7b481f015a0386f5ee3e13415474952653ddc198 🗣
-----BEGIN PGP SIGNATURE-----
iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUgMsAv+IixBito8qp9aWseDFUHP4taa+OQ4iO4dEf7Qt8MvAUY/vLXATBx4cuUJ
5ZORW+54b4ZYCV4Qw9I7FmxKZBY9ihREr7/JjvteAtJSVULv35s6zxJxESnec80q
76ynV3KYnKd6pNMCHmR1Gy2EyIAUtG1ttGHkE5TfkjfL/WVEBBC0Yziwc0Zc1Tr9
gcvyLBWP9h84Lo1KS6ul8NTAJvk7e1r401S9DsF+1mqMrMrLUCY/2l19VqUBV9J0
jsPJOO/LHcJbTt2biXx16NtVwCDbr590fZPSIvFJ7kmb4p0M3Fw0BI+EgucTSSKM
+JOTf+v31zuacSRjpgqmjKDNjb0rodKavcJEJG1IOwakCs4eZdwAx1W1y4Bh4672
K/Zhsb9cQl1VAVJrFLLNlinsi6kqz6490MfxKg7COHXz4YNaUuqpiulQo/zbbRW6
L1EhPhuKs42ZvHkRecOzi+wEC+8XvBIuQ+Bx5kD0oH530m697kBdnRgbAlKVB9yX
rr9jAuD7
=bTsy
-----END PGP SIGNATURE-----
7b481f0 Fix Racy ParseOpCode function initialization (Jeremy Rubin) Pull request description: If multiple callers call ParseOpCode concurrently it will cause a race condition. We can either move the static to it's own area and require init be called explicitly, or just allow concurrent first callers to race to fill in an atomic variable that never changes thereafter. The second approach is taken here. Static initialization *is* threadsafe, but only insofar as definining the variable being guaranteed to be called once. This is used incorrectly here. practicalswift --> are there tools we can deploy to catch usage like this? ACKs for top commit: MarcoFalke: re-ACK 7b481f0 🗣 Tree-SHA512: cbf9dc3af26d7335305026f32ce8472a018309b89b3d81a67357e59fbeed72c37b5b8a6e30325ea68145c3b2403867be82de01f22decefb6e6717cf0c0045633
If multiple callers call ParseOpCode concurrently it will cause a race condition. We can either move the static to it's own area and require init be called explicitly, or just allow concurrent first callers to race to fill in an atomic variable that never changes thereafter. The second approach is taken here.
Static initialization is threadsafe, but only insofar as definining the variable being guaranteed to be called once. This is used incorrectly here.
practicalswift --> are there tools we can deploy to catch usage like this?