-
Notifications
You must be signed in to change notification settings - Fork 37.7k
util: Safer MakeByteSpan with ByteSpanCast #27973
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
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process. 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. |
fad126d seems like a nice improvement. It makes existing MakeByteSpan function safer by only allowing it to be called on arrays of characters, and not array of other things like
Would suggest tweaking the PR as follows: --- a/src/span.h
+++ b/src/span.h
@@ -270,25 +270,28 @@ inline const unsigned char* UCharCast(const std::byte* c) { return reinterpret_c
// Helper function to safely convert a Span to a Span<[const] unsigned char>.
template <typename T> constexpr auto UCharSpanCast(Span<T> s) -> Span<typename std::remove_pointer<decltype(UCharCast(s.data()))>::type> { return {UCharCast(s.data()), s.size()}; }
-// Helper function to safely convert a Span to a Span<[const] std::byte>.
-template <typename B>
-auto ByteSpanCast(Span<B> s)
-{
- return Span{AsBytePtr(UCharCast(s.data())), s.size_bytes()}; // Use UCharCast to enforce B is a byte-like type.
-}
/** Like the Span constructor, but for (const) unsigned char member types only. Only works for (un)signed char containers. */
template <typename V> constexpr auto MakeUCharSpan(V&& v) -> decltype(UCharSpanCast(Span{std::forward<V>(v)})) { return UCharSpanCast(Span{std::forward<V>(v)}); }
+// Helper functions to turn arrays of characters into std::byte spans. Usable on
+// C++ std::string, std::string_view, and std::vector<char> objects, as well as
+// on C string literals and C fixed-size char[] arrays.
+//
+// Note that when constructing a span directly from a C string literal, the span
+// will include the \0 null byte. This can be avoided by constructing the span
+// indirectly through std::string_view.
template <typename V>
Span<const std::byte> MakeByteSpan(V&& v) noexcept
{
- return ByteSpanCast(Span{std::forward<V>(v)});
+ Span data{std::forward<V>(v)};
+ return AsBytes(Span{UCharCast(data.data()), data.size_bytes()});
}
template <typename V>
Span<std::byte> MakeWritableByteSpan(V&& v) noexcept
{
- return ByteSpanCast(Span{std::forward<V>(v)});
+ Span data{std::forward<V>(v)};
+ return AsWritableBytes(Span{UCharCast(data.data()), data.size_bytes()});
}
#endif // BITCOIN_SPAN_H |
🐙 This pull request conflicts with the target branch and needs rebase. |
Maybe MakeByteSpan should just be removed?
|
I don't think I agree.
I don't think it's pointless to have a safe function even though a dangerous alternative exists. If I'm writing or reviewing code that uses a safe implementation of I think it would be a good thing to discourage use of |
Would you be less confident if there was a clang-tidy plugin doing the check? For example, instead of having two functions that do a similar thing, but it is unclear which one is safer/preferred from just looking at the name, we could have an additional |
I think that sounds fine, though I'm not sure what the clang plugin adds exactly. If the goal is just to forbid I just think as long as we have a |
FWIW, I don't believe |
Yes that should be true as long as span is pointing to live memory. I should replace "platform specific or undefined" with "implementation specific" in the #27978 change description |
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.
General feed back have I have not given approval just yet on a merge. I have to review some more
This removes bloat that is not needed.
The AsBytes helpers (or std::as_bytes helpers) are architecture dependent. For example, the below test case diff [0], applied before this commit will pass on x86_64, but fail on s390x [1]. Fix this by replacing AsBytes with a new ByteSpanCast where possible. [0] test case diff: ``` diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index 09f77d2..4e56ffa351 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -241,6 +241,15 @@ BOOST_AUTO_TEST_CASE(class_methods) ss2 << intval << boolval << stringval << charstrval << txval; ss2 >> methodtest3; BOOST_CHECK(methodtest3 == methodtest4); + + { + DataStream out; + std::vector<uint8_t> in_8{0x00, 0x11, 0x22, 0x33}; + std::vector<uint16_t> in_16{0xaabb, 0xccdd}; + out.write(MakeByteSpan(in_8)); + out.write(MakeByteSpan(in_16)); + BOOST_CHECK_EQUAL(HexStr(out), "00112233bbaaddcc"); + } } BOOST_AUTO_TEST_SUITE_END() ``` [1] test case failure on s390x: test/serialize_tests.cpp(251): error: in "serialize_tests/class_methods": check HexStr(out) == "00112233bbaaddcc" has failed [00112233aabbccdd != 00112233bbaaddcc]
Closing for now to allow for more brainstorming |
7c85361 refactor: Drop unsafe AsBytePtr function (Ryan Ofsky) Pull request description: Replace calls to `AsBytePtr` with calls to `AsBytes` or `reinterpret_cast`. `AsBytePtr` is just a wrapper around `reinterpret_cast`. It accepts any type of pointer as an argument and uses `reinterpret_cast` to cast the argument to a `std::byte` pointer. Despite taking any type of pointer as an argument, it is not useful to call `AsBytePtr` on most types of pointers, because byte representations of most types will be platform specific or undefined. Also, because it is named similarly to the `AsBytes` function, `AsBytePtr` looks safer than it actually is. Both `AsBytes` and `AsBytePtr` call reinterpret_cast internally and may be unsafe to use with certain types, but AsBytes at least has some type checking and can only be called on `Span` objects, while `AsBytePtr` can be called on any pointer argument. The change was motivated by discussion on #27973 and #27927 and is compatible with those PRs ACKs for top commit: jonatack: re-ACK 7c85361 sipa: utACK 7c85361 achow101: ACK 7c85361 Tree-SHA512: 200d858b1d4d579f081a7f9a14d488a99713b4918b4564ac3dd5c18578d927dbd6426e62e02f49f04a3fa73ca02ff7109c495cb0b92bec43c27d9b74e2f95757
7c85361 refactor: Drop unsafe AsBytePtr function (Ryan Ofsky) Pull request description: Replace calls to `AsBytePtr` with calls to `AsBytes` or `reinterpret_cast`. `AsBytePtr` is just a wrapper around `reinterpret_cast`. It accepts any type of pointer as an argument and uses `reinterpret_cast` to cast the argument to a `std::byte` pointer. Despite taking any type of pointer as an argument, it is not useful to call `AsBytePtr` on most types of pointers, because byte representations of most types will be platform specific or undefined. Also, because it is named similarly to the `AsBytes` function, `AsBytePtr` looks safer than it actually is. Both `AsBytes` and `AsBytePtr` call reinterpret_cast internally and may be unsafe to use with certain types, but AsBytes at least has some type checking and can only be called on `Span` objects, while `AsBytePtr` can be called on any pointer argument. The change was motivated by discussion on bitcoin#27973 and bitcoin#27927 and is compatible with those PRs ACKs for top commit: jonatack: re-ACK 7c85361 sipa: utACK 7c85361 achow101: ACK 7c85361 Tree-SHA512: 200d858b1d4d579f081a7f9a14d488a99713b4918b4564ac3dd5c18578d927dbd6426e62e02f49f04a3fa73ca02ff7109c495cb0b92bec43c27d9b74e2f95757
The
AsBytes
helpers (orstd::as_bytes
helpers) are architecturedependent. For example, the below test case diff [0], applied before
this commit will pass on x86_64, but fail on s390x [1].
Fix this by replacing
AsBytes
with a newByteSpanCast
in theMakeByteSpan
helper.This will turn the test case diff into a compile failure instead of a runtime error.
[0] test case diff:
[1] test case failure on s390x:
test/serialize_tests.cpp(251): error: in "serialize_tests/class_methods": check HexStr(out) == "00112233bbaaddcc" has failed [00112233aabbccdd != 00112233bbaaddcc]