diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fed5cb..f757bc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ Please read the [Code of Conduct](CODE_OF_CONDUCT.md). * Radiant uses Bazel as the primary build system. Install Bazel ([official instructions][bazel.install]). [Bazelisk][bazel.bazelisk] is recommended. -* Python is required for tooling. Install Python +* Python (3.11+) is required for tooling. Install Python ([official instructions][python.install]). * Radiant uses python package to aid in the remaining setup and subsequent workflows. Install it into your python environment by running: diff --git a/SECURITY.md b/SECURITY.md index ef20119..c21c441 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,7 +19,7 @@ additional information or guidance. ## Disclosure Policy -When the Raditn team receives a security bug report, they will assign it to a +When the Radiant team receives a security bug report, they will assign it to a primary developer. This person will coordinate the fix and release process, involving the following steps: diff --git a/radiant/UniquePtr.h b/radiant/UniquePtr.h new file mode 100644 index 0000000..7582335 --- /dev/null +++ b/radiant/UniquePtr.h @@ -0,0 +1,169 @@ +// radiant/UniquePtr.h +// Copyright 2025 The Radiant Authors. +// 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 "radiant/Utility.h" // rad::Move +#include "radiant/Algorithm.h" // rad::Swap +#include "radiant/detail/StdTypeTraits.h" // rad::IsConv +#include // nullptr_t + +namespace rad +{ + +/// @brief Empty deleter type so it occupies no storage (EBO). +template +struct DefaultDelete { + constexpr DefaultDelete() noexcept = default; + + /// @brief Allow DefaultDelete to be constructed from DefaultDelete + template , int> = 0> + constexpr DefaultDelete(const DefaultDelete&) noexcept {} + + void operator()(T* p) const noexcept { delete p; } +}; + +/// @brief UniquePtr owns a T* and invokes Deleter when destroyed. +/// @details If Deleter is empty, sizeof(UniquePtr) == sizeof(T*). +template > +class UniquePtr : private Deleter +{ +public: + RAD_NOT_COPYABLE(UniquePtr); + + using pointer = T*; + using deleter_type = Deleter; + + /// @brief Default constructs to nullptr. + constexpr UniquePtr() noexcept + : Deleter(), // base subobject + m_ptr(nullptr) + {} + + /// @brief Constructs owning a raw pointer + optional custom deleter. + explicit constexpr UniquePtr(pointer p, Deleter d = Deleter()) noexcept + : Deleter(rad::Move(d)), + m_ptr(p) + {} + + /// @brief Constructs owning a raw pointer + optimal custom deleter. + template , int> = 0> + constexpr UniquePtr(UniquePtr&& o) noexcept + : Deleter(rad::Move(o.get_deleter())), + m_ptr(o.release()) + {} + + /// @brief Move-constructs, stealing ownership + constexpr UniquePtr(UniquePtr&& o) noexcept + : Deleter(rad::Move(o.get_deleter())), + m_ptr(o.release()) + { + o.m_ptr = nullptr; + } + + /// @brief Destroys the managed object if non-null. + ~UniquePtr() noexcept + { + if (m_ptr) static_cast(*this)(m_ptr); + } + + /// @brief nullptr-assignment (clears the pointer) + UniquePtr& operator=(std::nullptr_t) noexcept + { + reset(); + return *this; + } + + /// @brief Assigns a raw pointer, deleting the current one. + template , int> = 0> + UniquePtr& operator=(UniquePtr&& o) noexcept + { + reset(o.release()); + get_deleter() = rad::Move(o.get_deleter()); + return *this; + } + + /// @brief Move-assigns, destroying current and stealing ownership. + UniquePtr& operator=(UniquePtr&& o) noexcept + { + if (this != &o) + { + reset(); + m_ptr = o.m_ptr; + get_deleter() = rad::Move(o.get_deleter()); + o.m_ptr = nullptr; + } + return *this; + } + + // --- OBSERVERS --- + + /// @return the stored pointer (may be nullptr) + constexpr pointer get() const noexcept { return m_ptr; } + + /// @return reference to *get() + constexpr T& operator*() const noexcept { return *m_ptr; } + + /// @return get(), for pointer‐like syntax + constexpr pointer operator->() const noexcept { return m_ptr; } + + /// @return true if get() != nullptr + explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; } + + // --- MODIFIERS --- + + /// @brief Release ownership without deleting; returns previous pointer. + pointer release() noexcept + { + pointer tmp = m_ptr; + m_ptr = nullptr; + return tmp; + } + + /// @brief Delete current and take new pointer p (default nullptr) + void reset(pointer p = nullptr) noexcept + { + if (m_ptr) static_cast(*this)(m_ptr); + m_ptr = p; + } + + /// @brief Swap pointers and deleters with another UniquePtr + void swap(UniquePtr& o) noexcept + { + using rad::Swap; + Swap(m_ptr, o.m_ptr); + Swap(get_deleter(), o.get_deleter()); + } + + /// @return reference to the stored deleter + constexpr Deleter& get_deleter() noexcept { return static_cast(*this); } + constexpr const Deleter& get_deleter() const noexcept { return static_cast(*this); } + +private: + pointer m_ptr; +}; + +/// @brief ADL‐enabled swap overload +template +void swap(UniquePtr& a, UniquePtr& b) noexcept +{ + a.swap(b); +} + +/// @brief Alias using default deleter +template +using UniquePtrDefault = UniquePtr; + +} // namespace rad \ No newline at end of file diff --git a/test/test_UniquePtr.cpp b/test/test_UniquePtr.cpp new file mode 100644 index 0000000..0a1238c --- /dev/null +++ b/test/test_UniquePtr.cpp @@ -0,0 +1,158 @@ +// test/test_UniquePtr.cpp +// Copyright 2025 The Radiant Authors. +// 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 "gtest/gtest.h" +#include "radiant/UniquePtr.h" + +// simple type to track live instances +struct Foo { + static int live; + int value; + Foo(int v) : value(v) { ++live; } + ~Foo() { --live; } +}; +int Foo::live = 0; + +// custom deleter functor for testing +struct D { + static int count; + void operator()(int* p) const noexcept { + ++count; + delete p; + } +}; +int D::count = 0; + +TEST(UniquePtr, BasicLifetimeAndSize) { + // the default deleter is empty, so this must be one pointer in size + static_assert(sizeof(rad::UniquePtrDefault) == sizeof(Foo*), + "UniquePtrDefault must be one pointer"); + + EXPECT_EQ(Foo::live, 0); + { + rad::UniquePtrDefault p(new Foo{42}); + EXPECT_TRUE(p); + EXPECT_EQ(p->value, 42); + EXPECT_EQ(Foo::live, 1); + } + // destructor should have run + EXPECT_EQ(Foo::live, 0); +} + +TEST (UniquePtr, NullptrAssignment) { + rad::UniquePtrDefault p(new int{3}); + EXPECT_TRUE(p); + p = nullptr; + EXPECT_FALSE(p); +} + +TEST(UniquePtr, ReleaseAndReset) { + Foo::live = 0; + Foo* raw = new Foo{7}; + rad::UniquePtrDefault p(raw); + EXPECT_TRUE(p); + EXPECT_EQ(Foo::live, 1); + + Foo* r = p.release(); + EXPECT_EQ(r, raw); + EXPECT_FALSE(p); + // user must delete now: + delete r; + EXPECT_EQ(Foo::live, 0); + + // reset to new object + p.reset(new Foo{5}); + EXPECT_TRUE(p); + EXPECT_EQ(p->value, 5); + EXPECT_EQ(Foo::live, 1); + + // reset to nullptr + p.reset(); + EXPECT_FALSE(p); + EXPECT_EQ(Foo::live, 0); +} + +TEST(UniquePtr, MoveSemantics) { + Foo::live = 0; + rad::UniquePtrDefault a(new Foo{1}); + EXPECT_TRUE(a); + + rad::UniquePtrDefault b(std::move(a)); + EXPECT_FALSE(a); + EXPECT_TRUE(b); + EXPECT_EQ(b->value, 1); + + // move-assign + Foo::live = 1; + rad::UniquePtrDefault c; + c = std::move(b); + EXPECT_FALSE(b); + EXPECT_TRUE(c); + EXPECT_EQ(c->value, 1); + + // cleanup + c.reset(); + EXPECT_EQ(Foo::live, 0); +} + +struct Base { virtual ~Base() = default; }; +struct Derived : Base { int v = 9; }; + +TEST(UniquePtr, ConvertingMove) { + rad::UniquePtr d(new Derived{}); + rad::UniquePtr b(std::move(d)); + EXPECT_TRUE(b); + EXPECT_FALSE(d); + + rad::UniquePtr d2(new Derived{}); + b = std::move(d2); + EXPECT_TRUE(b); + EXPECT_FALSE(d2); +} + +TEST(UniquePtr, Swap) { + Foo::live = 0; + rad::UniquePtrDefault x(new Foo{10}); + rad::UniquePtrDefault y(new Foo{20}); + EXPECT_EQ(x->value, 10); + EXPECT_EQ(y->value, 20); + + x.swap(y); + EXPECT_EQ(x->value, 20); + EXPECT_EQ(y->value, 10); + + // ADL swap + using std::swap; + swap(x, y); + EXPECT_EQ(x->value, 10); + EXPECT_EQ(y->value, 20); + + // cleanup + x.reset(); + y.reset(); + EXPECT_EQ(Foo::live, 0); +} + +TEST(UniquePtr, CustomDeleter) { + D::count = 0; + int* raw = new int(99); + + // UniquePtr uses our functor D + rad::UniquePtr p(raw, D()); + EXPECT_TRUE(p); + EXPECT_EQ(*p, 99); + EXPECT_EQ(D::count, 0); + + p.reset(); + EXPECT_FALSE(p); + EXPECT_EQ(D::count, 1); +} \ No newline at end of file