//MIT License // //Copyright (c) 2019 Mindaugas Vinkelis // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. #include #include #include #include #include "serialization_test_utils.h" using bitsery::ext::BaseClass; using bitsery::ext::VirtualBaseClass; using bitsery::ext::InheritanceContext; using bitsery::ext::PointerLinkingContext; using bitsery::ext::PolymorphicContext; using bitsery::ext::StandardRTTI; using bitsery::ext::PointerOwner; using bitsery::ext::PointerObserver; using bitsery::ext::ReferencedByPointer; using bitsery::ext::StdSmartPtr; using testing::Eq; using TContext = std::tuple>; using SerContext = BasicSerializationContext; //this is useful for PolymorphicContext to bind classes to serializer/deserializer using TSerializer = typename SerContext::TSerializer; using TDeserializer = typename SerContext::TDeserializer; /* * base class */ struct Base { Base() = default; explicit Base(uint64_t v) : x{v} {} uint64_t x{}; virtual ~Base() = default; }; template void serialize(S& s, Base& o) { s.value8b(o.x); } struct Derived1 : Base { Derived1() = default; Derived1(uint64_t x_, uint64_t y_) : Base{x_}, y1{y_} {} friend bool operator==(const Derived1& lhs, const Derived1& rhs) { return lhs.x == rhs.x && lhs.y1 == rhs.y1; } uint64_t y1{}; }; template void serialize(S& s, Derived1& o) { s.ext(o, BaseClass{}); s.value8b(o.y1); } struct Derived2 : Base { uint64_t y1{}; uint64_t y2{}; }; template void serialize(S& s, Derived2& o) { s.ext(o, BaseClass{}); s.value8b(o.y1); s.value8b(o.y2); } // polymorphic structure that contains polymorphic pointer, to test memory resource propagation struct PolyPtrWithPolyPtrBase { std::unique_ptr ptr{}; virtual ~PolyPtrWithPolyPtrBase() = default; }; template void serialize(S& s, PolyPtrWithPolyPtrBase& o) { s.ext(o.ptr, StdSmartPtr{}); } struct DerivedPolyPtrWithPolyPtr : PolyPtrWithPolyPtrBase { }; template void serialize(S& s, DerivedPolyPtrWithPolyPtr& o) { s.ext(o.ptr, StdSmartPtr{}); } //define relationships between base class and derived classes for runtime polymorphism namespace bitsery { namespace ext { template<> struct PolymorphicBaseClass : PolymorphicDerivedClasses { }; template<> struct PolymorphicBaseClass : PolymorphicDerivedClasses { }; } } // this class is for testing struct TestAllocInfo { void* ptr; size_t bytes; size_t alignment; size_t typeId; friend bool operator==(const TestAllocInfo& lhs, const TestAllocInfo& rhs) { return std::tie(lhs.ptr, lhs.bytes, lhs.alignment, lhs.typeId) == std::tie(rhs.ptr, rhs.bytes, rhs.alignment, rhs.typeId); } }; struct MemResourceForTest : public bitsery::ext::MemResourceBase { void* allocate(size_t bytes, size_t alignment, size_t typeId) override { const auto res = bitsery::ext::MemResourceNewDelete{}.allocate(bytes, alignment, typeId); allocs.push_back({res, bytes, alignment, typeId}); return res; } void deallocate(void* ptr, size_t bytes, size_t alignment, size_t typeId) noexcept override { deallocs.push_back({ptr, bytes, alignment, typeId}); bitsery::ext::MemResourceNewDelete{}.deallocate(ptr, bytes, alignment, typeId); } std::vector allocs{}; std::vector deallocs{}; }; class SerializeExtensionPointerWithAllocator : public testing::Test { public: TContext plctx{}; SerContext sctx{}; typename SerContext::TSerializer& createSerializer() { auto& res = sctx.createSerializer(plctx); std::get<2>(plctx).clear(); //bind serializer with classes std::get<2>(plctx).registerBasesList( bitsery::ext::PolymorphicClassesList{}); return res; } typename SerContext::TDeserializer& createDeserializer() { auto& res = sctx.createDeserializer(plctx); std::get<2>(plctx).clear(); //bind deserializer with classes std::get<2>(plctx).registerBasesList( bitsery::ext::PolymorphicClassesList{}); return res; } bool isPointerContextValid() { return std::get<0>(plctx).isValid(); } virtual void TearDown() override { EXPECT_TRUE(isPointerContextValid()); } }; TEST_F(SerializeExtensionPointerWithAllocator, CanSetDefaultMemoryResourceInPointerLinkingContext) { MemResourceForTest memRes{}; std::get<0>(plctx).setMemResource(&memRes); Base* baseData = new Derived1{2, 1}; createSerializer().ext(baseData, PointerOwner{}); Base* baseRes = nullptr; createDeserializer().ext(baseRes, PointerOwner{}); auto dData = dynamic_cast(baseData); auto dRes = dynamic_cast(baseRes); EXPECT_THAT(dRes, ::testing::NotNull()); EXPECT_THAT(*dData, *dRes); EXPECT_THAT(memRes.allocs.size(), Eq(1u)); EXPECT_THAT(memRes.allocs[0].bytes, Eq(sizeof(Derived1))); EXPECT_THAT(memRes.allocs[0].alignment, Eq(alignof(Derived1))); EXPECT_THAT(memRes.allocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); EXPECT_THAT(memRes.deallocs.size(), Eq(0u)); delete dData; delete dRes; } TEST_F(SerializeExtensionPointerWithAllocator, CorrectlyDeallocatesPreviousInstance) { MemResourceForTest memRes{}; std::get<0>(plctx).setMemResource(&memRes); Base* baseData = new Derived1{2, 1}; createSerializer().ext(baseData, PointerOwner{}); Base* baseRes = new Derived2; createDeserializer().ext(baseRes, PointerOwner{}); auto dData = dynamic_cast(baseData); auto dRes = dynamic_cast(baseRes); EXPECT_THAT(dRes, ::testing::NotNull()); EXPECT_THAT(*dData, *dRes); EXPECT_THAT(memRes.allocs.size(), Eq(1u)); EXPECT_THAT(memRes.allocs[0].bytes, Eq(sizeof(Derived1))); EXPECT_THAT(memRes.allocs[0].alignment, Eq(alignof(Derived1))); EXPECT_THAT(memRes.allocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); EXPECT_THAT(memRes.deallocs.size(), Eq(1u)); EXPECT_THAT(memRes.deallocs[0].bytes, Eq(sizeof(Derived2))); EXPECT_THAT(memRes.deallocs[0].alignment, Eq(alignof(Derived2))); EXPECT_THAT(memRes.deallocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); delete dData; delete dRes; } TEST_F(SerializeExtensionPointerWithAllocator, DefaultDeleterIsNotUsedForStdUniquePtr) { MemResourceForTest memRes{}; std::get<0>(plctx).setMemResource(&memRes); std::unique_ptr baseData{}; createSerializer().ext(baseData, StdSmartPtr{}); auto baseRes = std::unique_ptr(new Derived1{45, 64}); createDeserializer().ext(baseRes, StdSmartPtr{}); EXPECT_THAT(memRes.allocs.size(), Eq(0u)); EXPECT_THAT(memRes.deallocs.size(), Eq(1u)); EXPECT_THAT(memRes.deallocs[0].bytes, Eq(sizeof(Derived1))); EXPECT_THAT(memRes.deallocs[0].alignment, Eq(alignof(Derived1))); EXPECT_THAT(memRes.deallocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); } struct CustomBaseDeleter { void operator()(Base* obj) { delete obj; } }; TEST_F(SerializeExtensionPointerWithAllocator, CustomDeleterIsNotUsedForStdUniquePtr) { MemResourceForTest memRes{}; std::get<0>(plctx).setMemResource(&memRes); std::unique_ptr baseData{}; createSerializer().ext(baseData, StdSmartPtr{}); auto baseRes = std::unique_ptr(new Derived1{45, 64}); createDeserializer().ext(baseRes, StdSmartPtr{}); EXPECT_THAT(memRes.allocs.size(), Eq(0u)); EXPECT_THAT(memRes.deallocs.size(), Eq(1u)); EXPECT_THAT(memRes.deallocs[0].bytes, Eq(sizeof(Derived1))); EXPECT_THAT(memRes.deallocs[0].alignment, Eq(alignof(Derived1))); EXPECT_THAT(memRes.deallocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); } TEST_F(SerializeExtensionPointerWithAllocator, CanSetMemResourcePerPointer) { MemResourceForTest memRes1{}; MemResourceForTest memRes2{}; std::get<0>(plctx).setMemResource(&memRes1); Base* baseData = new Derived1{2, 1}; createSerializer().ext(baseData, PointerOwner{bitsery::ext::PointerType::Nullable, &memRes2}); Base* baseRes = new Derived2; createDeserializer().ext(baseRes, PointerOwner{bitsery::ext::PointerType::Nullable, &memRes2}); auto dData = dynamic_cast(baseData); auto dRes = dynamic_cast(baseRes); EXPECT_THAT(dRes, ::testing::NotNull()); EXPECT_THAT(*dData, *dRes); EXPECT_THAT(memRes1.allocs.size(), Eq(0u)); EXPECT_THAT(memRes1.deallocs.size(), Eq(0u)); EXPECT_THAT(memRes2.allocs.size(), Eq(1u)); EXPECT_THAT(memRes2.allocs[0].bytes, Eq(sizeof(Derived1))); EXPECT_THAT(memRes2.allocs[0].alignment, Eq(alignof(Derived1))); EXPECT_THAT(memRes2.allocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); EXPECT_THAT(memRes2.deallocs.size(), Eq(1u)); EXPECT_THAT(memRes2.deallocs[0].bytes, Eq(sizeof(Derived2))); EXPECT_THAT(memRes2.deallocs[0].alignment, Eq(alignof(Derived2))); EXPECT_THAT(memRes2.deallocs[0].typeId, Eq(bitsery::ext::StandardRTTI::get())); delete dData; delete dRes; } TEST_F(SerializeExtensionPointerWithAllocator, MemResourceSetPerPointerByDefaultDoNotPropagate) { MemResourceForTest memRes1{}; MemResourceForTest memRes2{}; std::get<0>(plctx).setMemResource(&memRes1); auto data = std::unique_ptr(new PolyPtrWithPolyPtrBase{}); data->ptr = std::unique_ptr(new Derived1{5, 6}); createSerializer().ext(data, StdSmartPtr{bitsery::ext::PointerType::Nullable, &memRes2}); auto res = std::unique_ptr(new DerivedPolyPtrWithPolyPtr{}); res->ptr = std::unique_ptr(new Derived2{}); createDeserializer().ext(res, StdSmartPtr{bitsery::ext::PointerType::Nullable, &memRes2}); EXPECT_THAT(memRes1.allocs.size(), Eq(1u)); // Base* was destroyed by unique_ptr on PolyPtrWithPolyPtrBase destructor, hence == 0 EXPECT_THAT(memRes1.deallocs.size(), Eq(0u)); EXPECT_THAT(memRes2.allocs.size(), Eq(1u)); EXPECT_THAT(memRes2.deallocs.size(), Eq(1u)); } TEST_F(SerializeExtensionPointerWithAllocator, MemResourceSetPerPointerCanPropagate) { MemResourceForTest memRes1{}; MemResourceForTest memRes2{}; std::get<0>(plctx).setMemResource(&memRes1); auto data = std::unique_ptr(new PolyPtrWithPolyPtrBase{}); data->ptr = std::unique_ptr(new Derived1{5, 6}); createSerializer().ext(data, StdSmartPtr{bitsery::ext::PointerType::Nullable, &memRes2, true}); auto res = std::unique_ptr(new DerivedPolyPtrWithPolyPtr{}); res->ptr = std::unique_ptr(new Derived2{}); createDeserializer().ext(res, StdSmartPtr{bitsery::ext::PointerType::Nullable, &memRes2, true}); EXPECT_THAT(memRes1.allocs.size(), Eq(0u)); EXPECT_THAT(memRes1.deallocs.size(), Eq(0u)); EXPECT_THAT(memRes2.allocs.size(), Eq(2u)); // deallocates are actually == 1, because when we destroy PolyPtrWithPolyPtrBase // it also destroys Base because it is managed by unique_ptr. // in order to do it correctly we should always use custom deleter for structures with nested pointers EXPECT_THAT(memRes2.deallocs.size(), Eq(1u)); }