-
-
Notifications
You must be signed in to change notification settings - Fork 16
Implement a new OwnedOrReference memory utility
#2774
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME memory | ||
| PRIVATE_HEADERS owned_or_reference.h) | ||
|
|
||
| if(SOURCEMETA_CORE_INSTALL) | ||
| sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME memory) | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| #ifndef SOURCEMETA_CORE_MEMORY_H_ | ||
| #define SOURCEMETA_CORE_MEMORY_H_ | ||
|
|
||
| // NOLINTBEGIN(misc-include-cleaner) | ||
| #include <sourcemeta/core/memory_owned_or_reference.h> | ||
| // NOLINTEND(misc-include-cleaner) | ||
|
|
||
| /// @defgroup memory Memory | ||
| /// @brief Growing collection of utilities for value ownership and lifetime | ||
| /// | ||
| /// This functionality is included as follows: | ||
| /// | ||
| /// ```cpp | ||
| /// #include <sourcemeta/core/memory.h> | ||
| /// ``` | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,124 @@ | ||||||||||||||||||||||||||||
| #ifndef SOURCEMETA_CORE_MEMORY_OWNED_OR_REFERENCE_H_ | ||||||||||||||||||||||||||||
| #define SOURCEMETA_CORE_MEMORY_OWNED_OR_REFERENCE_H_ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #include <cassert> // assert | ||||||||||||||||||||||||||||
| #include <concepts> // std::move_constructible, std::copy_constructible | ||||||||||||||||||||||||||||
| #include <memory> // std::addressof | ||||||||||||||||||||||||||||
| #include <optional> // std::optional, std::nullopt_t | ||||||||||||||||||||||||||||
| #include <type_traits> // std::is_object_v | ||||||||||||||||||||||||||||
| #include <utility> // std::move | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| namespace sourcemeta::core { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// @ingroup memory | ||||||||||||||||||||||||||||
| /// What a value that can be either owned or referred to must satisfy | ||||||||||||||||||||||||||||
| template <typename T> | ||||||||||||||||||||||||||||
| concept Ownable = std::is_object_v<T> && std::move_constructible<T>; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// @ingroup memory | ||||||||||||||||||||||||||||
| /// Either a value this holds itself, a reference to one that outlives it, or | ||||||||||||||||||||||||||||
| /// nothing at all. For example: | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// ```cpp | ||||||||||||||||||||||||||||
| /// #include <sourcemeta/core/memory.h> | ||||||||||||||||||||||||||||
| /// #include <cassert> | ||||||||||||||||||||||||||||
| /// #include <string> | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// static const std::string CACHED{"foo"}; | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// const sourcemeta::core::OwnedOrReference<std::string> reference{CACHED}; | ||||||||||||||||||||||||||||
| /// assert(&reference.value() == &CACHED); | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// const sourcemeta::core::OwnedOrReference<std::string> owned{ | ||||||||||||||||||||||||||||
| /// std::string{"bar"}}; | ||||||||||||||||||||||||||||
| /// assert(owned.value() == "bar"); | ||||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// Reach for this when a function sometimes materialises its result and | ||||||||||||||||||||||||||||
| /// sometimes hands back something it already has. Producers that build a | ||||||||||||||||||||||||||||
| /// value, by reading a file, performing a network request, or computing it, | ||||||||||||||||||||||||||||
| /// return it as they always would. Producers backed by storage that outlives | ||||||||||||||||||||||||||||
| /// the call, such as a long lived cache, return a reference instead and skip | ||||||||||||||||||||||||||||
| /// the copy. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// A reference must stay put and stay alive for as long as the consumer reads | ||||||||||||||||||||||||||||
| /// it. Anything temporary binds to the owning constructor, so a temporary can | ||||||||||||||||||||||||||||
| /// never be captured by reference here. A value that another one of these owns | ||||||||||||||||||||||||||||
| /// does not qualify either, as assigning to that one destroys what it holds. | ||||||||||||||||||||||||||||
| template <Ownable T> class OwnedOrReference { | ||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||
| /// Hold nothing | ||||||||||||||||||||||||||||
| OwnedOrReference() = default; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Hold nothing | ||||||||||||||||||||||||||||
| // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) | ||||||||||||||||||||||||||||
| OwnedOrReference(std::nullopt_t) {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Take ownership of a value that may or may not be there | ||||||||||||||||||||||||||||
| // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) | ||||||||||||||||||||||||||||
| OwnedOrReference(std::optional<T> &&value) : owned_{std::move(value)} {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Take ownership of a value | ||||||||||||||||||||||||||||
| // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) | ||||||||||||||||||||||||||||
| OwnedOrReference(T &&value) : owned_{std::move(value)} {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Refer to a value that outlives this. Anything temporary binds to the | ||||||||||||||||||||||||||||
| /// owning constructor above instead, so this never refers to a dead value | ||||||||||||||||||||||||||||
| // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) | ||||||||||||||||||||||||||||
| OwnedOrReference(const T &value) : referenced_{std::addressof(value)} {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // A constant temporary would otherwise bind to the referencing constructor | ||||||||||||||||||||||||||||
| // and leave a dangling pointer behind | ||||||||||||||||||||||||||||
| OwnedOrReference(const T &&value) = delete; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Prevent accidental copies, as copying is the very thing this type exists | ||||||||||||||||||||||||||||
| // to avoid. Take ownership through `to_owned` instead | ||||||||||||||||||||||||||||
| OwnedOrReference(const OwnedOrReference &) = delete; | ||||||||||||||||||||||||||||
| auto operator=(const OwnedOrReference &) -> OwnedOrReference & = delete; | ||||||||||||||||||||||||||||
| /// Move | ||||||||||||||||||||||||||||
| OwnedOrReference(OwnedOrReference &&) = default; | ||||||||||||||||||||||||||||
| /// Move | ||||||||||||||||||||||||||||
| auto operator=(OwnedOrReference &&) -> OwnedOrReference & = default; | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When a borrowed Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||
| ~OwnedOrReference() = default; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Whether there is anything to read | ||||||||||||||||||||||||||||
| [[nodiscard]] auto has_value() const noexcept -> bool { | ||||||||||||||||||||||||||||
| return this->referenced_ != nullptr || this->owned_.has_value(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Read the value, however it is held | ||||||||||||||||||||||||||||
| [[nodiscard]] auto value() const -> const T & { | ||||||||||||||||||||||||||||
| assert(this->has_value()); | ||||||||||||||||||||||||||||
| return this->referenced_ != nullptr ? *this->referenced_ | ||||||||||||||||||||||||||||
| : this->owned_.value(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Read the value, however it is held | ||||||||||||||||||||||||||||
| [[nodiscard]] auto operator*() const -> const T & { return this->value(); } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Read the value, however it is held | ||||||||||||||||||||||||||||
| [[nodiscard]] auto operator->() const -> const T * { | ||||||||||||||||||||||||||||
| return std::addressof(this->value()); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Get a value the caller owns, moving out of this one when it owns it and | ||||||||||||||||||||||||||||
| /// copying only when it holds a reference | ||||||||||||||||||||||||||||
| [[nodiscard]] auto to_owned() && -> T | ||||||||||||||||||||||||||||
| requires std::copy_constructible<T> | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Prompt for AI agents |
||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| assert(this->has_value()); | ||||||||||||||||||||||||||||
| if (this->referenced_ != nullptr) { | ||||||||||||||||||||||||||||
| return *this->referenced_; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return std::move(this->owned_).value(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private: | ||||||||||||||||||||||||||||
| std::optional<T> owned_; | ||||||||||||||||||||||||||||
| const T *referenced_{nullptr}; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| } // namespace sourcemeta::core | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| sourcemeta_test(NAMESPACE sourcemeta PROJECT core NAME memory | ||
| SOURCES memory_owned_or_reference_test.cc) | ||
|
|
||
| target_link_libraries(sourcemeta_core_memory_unit PRIVATE sourcemeta::core::memory) |
Uh oh!
There was an error while loading. Please reload this page.
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.
At
src/lang/memory/include/sourcemeta/core/memory_owned_or_reference.h:76, when the source borrows the target’s owned value (for example,OwnedOrReference<T> borrowed{target.value()}; target = std::move(borrowed);), memberwise assignment clearstarget.owned_before copyingborrowed.referenced_. The target then selects a dangling pointer invalue(), so this publicly constructible aliasing case has use-after-free behavior.Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.