diff options
author | 2022-10-10 10:55:40 -0400 | |
---|---|---|
committer | 2022-10-18 19:03:49 -0400 | |
commit | 04534e26ed13397d0a1f244d71ed758f368d5553 (patch) | |
tree | be88da3b3e1853fe624d9346e16ce670bd9845d7 | |
parent | b7d92d9f73ed78732a73927cbbad2b6a96d29bf4 (diff) |
FTL: Add invariant for non-null pointers
Upcasting is not supported for now.
Bug: 185536303
Test: ftl_test
Change-Id: Id37d3e9a4a794291417405eb2a8a300a9d2bfe72
-rw-r--r-- | include/ftl/non_null.h | 116 | ||||
-rw-r--r-- | libs/ftl/Android.bp | 1 | ||||
-rw-r--r-- | libs/ftl/non_null_test.cpp | 75 |
3 files changed, 192 insertions, 0 deletions
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h new file mode 100644 index 0000000000..35d09d71de --- /dev/null +++ b/include/ftl/non_null.h @@ -0,0 +1,116 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cstdlib> +#include <type_traits> +#include <utility> + +namespace android::ftl { + +// Enforces and documents non-null pre/post-condition for (raw or smart) pointers. +// +// void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr, +// ftl::NonNull<std::size_t*> length_ptr) { +// // No need for `nullptr` checks. +// *length_ptr = string_ptr->length(); +// } +// +// const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android")); +// std::size_t size; +// get_length(string_ptr, ftl::as_non_null(&size)); +// assert(size == 7u); +// +// For compatibility with std::unique_ptr<T> and performance with std::shared_ptr<T>, move +// operations are allowed despite breaking the invariant: +// +// using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>; +// +// Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> non_null_ptr, bool condition) { +// // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point. +// auto unique_ptr = std::move(non_null_ptr).take(); +// +// auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr<int>(std::move(unique_ptr))); +// auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr; +// +// return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)}; +// } +// +// auto ptr = ftl::as_non_null(std::make_unique<int>(42)); +// const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true); +// assert(ptr1.get() == ptr2); +// +template <typename Pointer> +class NonNull final { + struct Passkey {}; + + public: + // Disallow `nullptr` explicitly for clear compilation errors. + NonNull() = delete; + NonNull(std::nullptr_t) = delete; + + // Copy operations. + + constexpr NonNull(const NonNull&) = default; + constexpr NonNull& operator=(const NonNull&) = default; + + constexpr const Pointer& get() const { return pointer_; } + constexpr explicit operator const Pointer&() const { return get(); } + + // Move operations. These break the invariant, so care must be taken to avoid subsequent access. + + constexpr NonNull(NonNull&&) = default; + constexpr NonNull& operator=(NonNull&&) = default; + + constexpr Pointer take() && { return std::move(pointer_); } + constexpr explicit operator Pointer() && { return take(); } + + // Dereferencing. + constexpr decltype(auto) operator*() const { return *get(); } + constexpr decltype(auto) operator->() const { return get(); } + + // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions + // through the passkey idiom, for clear compilation errors. + template <typename P> + constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) { + if (!pointer_) std::abort(); + } + + private: + template <typename P> + friend constexpr auto as_non_null(P&&) -> NonNull<std::decay_t<P>>; + + Pointer pointer_; +}; + +template <typename P> +constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> { + using Passkey = typename NonNull<std::decay_t<P>>::Passkey; + return {Passkey{}, std::forward<P>(pointer)}; +} + +template <typename P, typename Q> +constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return lhs.get() == rhs.get(); +} + +template <typename P, typename Q> +constexpr bool operator!=(const NonNull<P>& lhs, const NonNull<Q>& rhs) { + return !operator==(lhs, rhs); +} + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index c1945fddb3..81113bc211 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -22,6 +22,7 @@ cc_test { "flags_test.cpp", "future_test.cpp", "match_test.cpp", + "non_null_test.cpp", "optional_test.cpp", "small_map_test.cpp", "small_vector_test.cpp", diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp new file mode 100644 index 0000000000..bd0462b3b6 --- /dev/null +++ b/libs/ftl/non_null_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ftl/non_null.h> +#include <gtest/gtest.h> + +#include <memory> +#include <string> +#include <string_view> + +namespace android::test { +namespace { + +void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr, + ftl::NonNull<std::size_t*> length_ptr) { + // No need for `nullptr` checks. + *length_ptr = string_ptr->length(); +} + +using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>; + +Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> non_null_ptr, bool condition) { + // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point. + auto unique_ptr = std::move(non_null_ptr).take(); + + auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr<int>(std::move(unique_ptr))); + auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr; + + return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)}; +} + +} // namespace + +// Keep in sync with example usage in header file. +TEST(NonNull, Example) { + const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android")); + std::size_t size; + get_length(string_ptr, ftl::as_non_null(&size)); + EXPECT_EQ(size, 7u); + + auto ptr = ftl::as_non_null(std::make_unique<int>(42)); + const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true); + EXPECT_EQ(ptr1.get(), ptr2); +} + +namespace { + +constexpr std::string_view kApple = "apple"; +constexpr std::string_view kOrange = "orange"; + +using StringViewPtr = ftl::NonNull<const std::string_view*>; +constexpr StringViewPtr kApplePtr = ftl::as_non_null(&kApple); +constexpr StringViewPtr kOrangePtr = ftl::as_non_null(&kOrange); + +constexpr StringViewPtr longest(StringViewPtr ptr1, StringViewPtr ptr2) { + return ptr1->length() > ptr2->length() ? ptr1 : ptr2; +} + +static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr); + +} // namespace +} // namespace android::test |