| // Copyright 2021 The libgav1 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 "src/utils/memory.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <new> | |
| #include "absl/base/config.h" | |
| #include "gtest/gtest.h" | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| #include <exception> | |
| #endif | |
| namespace libgav1 { | |
| namespace { | |
| constexpr size_t kMaxAllocableSize = 0x40000000; | |
| struct Small : public Allocable { | |
| uint8_t x; | |
| }; | |
| struct Huge : public Allocable { | |
| uint8_t x[kMaxAllocableSize + 1]; | |
| }; | |
| struct SmallMaxAligned : public MaxAlignedAllocable { | |
| alignas(kMaxAlignment) uint8_t x; | |
| }; | |
| struct HugeMaxAligned : public MaxAlignedAllocable { | |
| alignas(kMaxAlignment) uint8_t x[kMaxAllocableSize + 1]; | |
| }; | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| struct ThrowingConstructor : public Allocable { | |
| ThrowingConstructor() { throw std::exception(); } | |
| uint8_t x; | |
| }; | |
| struct MaxAlignedThrowingConstructor : public MaxAlignedAllocable { | |
| MaxAlignedThrowingConstructor() { throw std::exception(); } | |
| uint8_t x; | |
| }; | |
| #endif | |
| TEST(MemoryTest, TestAlignedAllocFree) { | |
| for (size_t alignment = 1; alignment <= 1 << 20; alignment <<= 1) { | |
| void* p = AlignedAlloc(alignment, 1); | |
| // Note this additional check is to avoid an incorrect static-analysis | |
| // warning for leaked memory with a plain ASSERT_NE(). | |
| if (p == nullptr) { | |
| FAIL() << "AlignedAlloc(" << alignment << ", 1)"; | |
| } | |
| const auto p_value = reinterpret_cast<uintptr_t>(p); | |
| EXPECT_EQ(p_value % alignment, 0) | |
| << "AlignedAlloc(" << alignment << ", 1) = " << p; | |
| AlignedFree(p); | |
| } | |
| } | |
| TEST(MemoryTest, TestAlignedUniquePtrAlloc) { | |
| for (size_t alignment = 1; alignment <= 1 << 20; alignment <<= 1) { | |
| auto p = MakeAlignedUniquePtr<uint8_t>(alignment, 1); | |
| ASSERT_NE(p, nullptr) << "MakeAlignedUniquePtr(" << alignment << ", 1)"; | |
| const auto p_value = reinterpret_cast<uintptr_t>(p.get()); | |
| EXPECT_EQ(p_value % alignment, 0) | |
| << "MakeAlignedUniquePtr(" << alignment << ", 1) = " << p.get(); | |
| } | |
| } | |
| TEST(MemoryTest, TestAllocable) { | |
| // Allocable::operator new (std::nothrow) is called. | |
| std::unique_ptr<Small> small(new (std::nothrow) Small); | |
| EXPECT_NE(small, nullptr); | |
| // Allocable::operator delete is called. | |
| small = nullptr; | |
| // Allocable::operator new[] (std::nothrow) is called. | |
| std::unique_ptr<Small[]> small_array_of_smalls(new (std::nothrow) Small[10]); | |
| EXPECT_NE(small_array_of_smalls, nullptr); | |
| // Allocable::operator delete[] is called. | |
| small_array_of_smalls = nullptr; | |
| // Allocable::operator new (std::nothrow) is called. | |
| std::unique_ptr<Huge> huge(new (std::nothrow) Huge); | |
| EXPECT_EQ(huge, nullptr); | |
| // Allocable::operator new[] (std::nothrow) is called. | |
| std::unique_ptr<Small[]> huge_array_of_smalls( | |
| new (std::nothrow) Small[kMaxAllocableSize / sizeof(Small) + 1]); | |
| EXPECT_EQ(huge_array_of_smalls, nullptr); | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| try { | |
| // Allocable::operator new (std::nothrow) is called. | |
| // The constructor throws an exception. | |
| // Allocable::operator delete (std::nothrow) is called. | |
| ThrowingConstructor* always = new (std::nothrow) ThrowingConstructor; | |
| static_cast<void>(always); | |
| } catch (...) { | |
| } | |
| try { | |
| // Allocable::operator new[] (std::nothrow) is called. | |
| // The constructor throws an exception. | |
| // Allocable::operator delete[] (std::nothrow) is called. | |
| ThrowingConstructor* always = new (std::nothrow) ThrowingConstructor[2]; | |
| static_cast<void>(always); | |
| } catch (...) { | |
| } | |
| #endif // ABSL_HAVE_EXCEPTIONS | |
| } | |
| TEST(MemoryTest, TestMaxAlignedAllocable) { | |
| // MaxAlignedAllocable::operator new (std::nothrow) is called. | |
| std::unique_ptr<SmallMaxAligned> small(new (std::nothrow) SmallMaxAligned); | |
| EXPECT_NE(small, nullptr); | |
| // Note this check doesn't guarantee conformance as a suitably aligned | |
| // address may be returned from any allocator. | |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(small.get()) & (kMaxAlignment - 1), 0); | |
| // MaxAlignedAllocable::operator delete is called. | |
| small = nullptr; | |
| // MaxAlignedAllocable::operator new[] (std::nothrow) is called. | |
| std::unique_ptr<SmallMaxAligned[]> small_array_of_smalls( | |
| new (std::nothrow) SmallMaxAligned[10]); | |
| EXPECT_NE(small_array_of_smalls, nullptr); | |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(small_array_of_smalls.get()) & | |
| (kMaxAlignment - 1), | |
| 0); | |
| // MaxAlignedAllocable::operator delete[] is called. | |
| small_array_of_smalls = nullptr; | |
| // MaxAlignedAllocable::operator new (std::nothrow) is called. | |
| std::unique_ptr<HugeMaxAligned> huge(new (std::nothrow) HugeMaxAligned); | |
| EXPECT_EQ(huge, nullptr); | |
| // MaxAlignedAllocable::operator new[] (std::nothrow) is called. | |
| std::unique_ptr<SmallMaxAligned[]> huge_array_of_smalls( | |
| new (std::nothrow) | |
| SmallMaxAligned[kMaxAllocableSize / sizeof(SmallMaxAligned) + 1]); | |
| EXPECT_EQ(huge_array_of_smalls, nullptr); | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| try { | |
| // MaxAlignedAllocable::operator new (std::nothrow) is called. | |
| // The constructor throws an exception. | |
| // MaxAlignedAllocable::operator delete (std::nothrow) is called. | |
| auto* always = new (std::nothrow) MaxAlignedThrowingConstructor; | |
| static_cast<void>(always); | |
| } catch (...) { | |
| } | |
| try { | |
| // MaxAlignedAllocable::operator new[] (std::nothrow) is called. | |
| // The constructor throws an exception. | |
| // MaxAlignedAllocable::operator delete[] (std::nothrow) is called. | |
| auto* always = new (std::nothrow) MaxAlignedThrowingConstructor[2]; | |
| static_cast<void>(always); | |
| } catch (...) { | |
| } | |
| #endif // ABSL_HAVE_EXCEPTIONS | |
| } | |
| } // namespace | |
| } // namespace libgav1 |