Refactor/any soo (#3564)

* refactor(Any): SOO

- encapsulate data holders
- add missing gets and ops
- eliminate g++ warnings with enable_if's
- default enable SOO

* refactor(Placeholder): encapsulate SOO memory management and fix leaks; cf. #3297 #3514

* fix(Placeholder): asan errors and add tests

cf. #3297 #3514
This commit is contained in:
Aleksandar Fabijanic
2022-04-16 06:38:55 -05:00
committed by GitHub
parent 7ae6b60e9f
commit 9c976da830
9 changed files with 264 additions and 119 deletions

View File

@@ -38,6 +38,16 @@ template <class T> class VarHolderImpl;
} }
template <class T, std::size_t S>
struct TypeSizeLE:
std::integral_constant<bool, (sizeof(T) <= S)>{};
template <class T, std::size_t S>
struct TypeSizeGT:
std::integral_constant<bool, (sizeof(T) > S)>{};
#ifndef POCO_NO_SOO #ifndef POCO_NO_SOO
@@ -58,14 +68,38 @@ public:
static const unsigned int value = SizeV; static const unsigned int value = SizeV;
}; };
Placeholder() Placeholder(const Placeholder&) = delete;
Placeholder(Placeholder&&) = delete;
Placeholder& operator=(const Placeholder&) = delete;
Placeholder& operator=(Placeholder&&) = delete;
Placeholder(): pHolder(0)
{ {
erase(); std::memset(holder, 0, sizeof(Placeholder));
}
~Placeholder()
{
destruct(false);
}
void swap(Placeholder& other)
{
if (!isLocal() && !other.isLocal())
std::swap(pHolder, other.pHolder);
else
throw Poco::InvalidAccessException("Placeholder::swap()");
} }
void erase() void erase()
{ {
std::memset(holder, 0, sizeof(Placeholder)); destruct(true);
}
bool isEmpty() const
{
char buf[POCO_SMALL_OBJECT_SIZE] = {};
return 0 == std::memcmp(holder, buf, POCO_SMALL_OBJECT_SIZE);
} }
bool isLocal() const bool isLocal() const
@@ -78,6 +112,24 @@ public:
holder[SizeV] = local ? 1 : 0; holder[SizeV] = local ? 1 : 0;
} }
template <typename T, typename V>
PlaceholderT* assignStack(const V& value)
{
erase();
new (reinterpret_cast<PlaceholderT*>(holder)) T(value);
setLocal(true);
return reinterpret_cast<PlaceholderT*>(holder);
}
template <typename T, typename V>
PlaceholderT* assignHeap(const V& value)
{
erase();
pHolder = new T(value);
setLocal(false);
return pHolder;
}
PlaceholderT* content() const PlaceholderT* content() const
{ {
if (isLocal()) if (isLocal())
@@ -86,20 +138,25 @@ public:
return pHolder; return pHolder;
} }
// MSVC71,80 won't extend friendship to nested class (Any::Holder)
#if !defined(POCO_MSVC_VERSION) || (defined(POCO_MSVC_VERSION) && (POCO_MSVC_VERSION > 80))
private: private:
#endif typedef typename std::aligned_storage<SizeV+1>::type AlignerType;
typedef typename std::aligned_storage<SizeV + 1>::type AlignerType;
PlaceholderT* pHolder; void destruct(bool clear)
mutable char holder[SizeV + 1]; {
AlignerType aligner; if (!isEmpty())
{
if (!isLocal())
delete pHolder;
else
reinterpret_cast<PlaceholderT*>(holder)->~PlaceholderT();
friend class Any; if (clear) std::memset(holder, 0, sizeof(Placeholder));
friend class Dynamic::Var; }
friend class Dynamic::VarHolder; }
template <class> friend class Dynamic::VarHolderImpl;
PlaceholderT* pHolder;
mutable unsigned char holder[SizeV+1];
AlignerType aligner;
}; };
@@ -185,13 +242,6 @@ public:
/// Destructor. If Any is locally held, calls ValueHolder destructor; /// Destructor. If Any is locally held, calls ValueHolder destructor;
/// otherwise, deletes the placeholder from the heap. /// otherwise, deletes the placeholder from the heap.
{ {
if (!empty())
{
if (_valueHolder.isLocal())
destruct();
else
delete content();
}
} }
Any& swap(Any& other) Any& swap(Any& other)
@@ -205,14 +255,13 @@ public:
if (!_valueHolder.isLocal() && !other._valueHolder.isLocal()) if (!_valueHolder.isLocal() && !other._valueHolder.isLocal())
{ {
std::swap(_valueHolder.pHolder, other._valueHolder.pHolder); _valueHolder.swap(other._valueHolder);
} }
else else
{ {
Any tmp(*this); Any tmp(*this);
try try
{ {
if (_valueHolder.isLocal()) destruct();
construct(other); construct(other);
other = tmp; other = tmp;
} }
@@ -252,8 +301,7 @@ public:
bool empty() const bool empty() const
/// Returns true if the Any is empty. /// Returns true if the Any is empty.
{ {
char buf[POCO_SMALL_OBJECT_SIZE] = { 0 }; return _valueHolder.isEmpty();
return 0 == std::memcmp(_valueHolder.holder, buf, POCO_SMALL_OBJECT_SIZE);
} }
const std::type_info & type() const const std::type_info & type() const
@@ -290,21 +338,27 @@ private:
virtual void clone(Placeholder<ValueHolder>* pPlaceholder) const virtual void clone(Placeholder<ValueHolder>* pPlaceholder) const
{ {
if ((sizeof(Holder<ValueType>) <= POCO_SMALL_OBJECT_SIZE)) cloneSOO(pPlaceholder, _held);
{
new ((ValueHolder*) pPlaceholder->holder) Holder(_held);
pPlaceholder->setLocal(true);
}
else
{
pPlaceholder->pHolder = new Holder(_held);
pPlaceholder->setLocal(false);
}
} }
ValueType _held; ValueType _held;
private: private:
template<typename VT,
typename std::enable_if<TypeSizeLE<Holder<VT>, Placeholder<VT>::Size::value>::value>::type* = nullptr>
static void cloneSOO(Placeholder<ValueHolder>* pPlaceholder, VT& held)
{
pPlaceholder->assignStack<Holder<ValueType>, ValueType>(held);
}
template<typename VT,
typename std::enable_if<TypeSizeGT<Holder<VT>, Placeholder<VT>::Size::value>::value>::type* = nullptr>
static void cloneSOO(Placeholder<ValueHolder>* pPlaceholder, VT& held)
{
pPlaceholder->assignHeap<Holder<ValueType>, ValueType>(held);
}
Holder & operator = (const Holder &); Holder & operator = (const Holder &);
}; };
@@ -313,19 +367,18 @@ private:
return _valueHolder.content(); return _valueHolder.content();
} }
template<typename ValueType> template<typename ValueType,
typename std::enable_if<TypeSizeLE<Holder<ValueType>, Placeholder<ValueType>::Size::value>::value>::type* = nullptr>
void construct(const ValueType& value) void construct(const ValueType& value)
{ {
if (sizeof(Holder<ValueType>) <= Placeholder<ValueType>::Size::value) _valueHolder.assignStack<Holder<ValueType>, ValueType>(value);
{ }
new (reinterpret_cast<ValueHolder*>(_valueHolder.holder)) Holder<ValueType>(value);
_valueHolder.setLocal(true); template<typename ValueType,
} typename std::enable_if<TypeSizeGT<Holder<ValueType>, Placeholder<ValueType>::Size::value>::value>::type* = nullptr>
else void construct(const ValueType& value)
{ {
_valueHolder.pHolder = new Holder<ValueType>(value); _valueHolder.assignHeap<Holder<ValueType>, ValueType>(value);
_valueHolder.setLocal(false);
}
} }
void construct(const Any& other) void construct(const Any& other)
@@ -336,11 +389,6 @@ private:
_valueHolder.erase(); _valueHolder.erase();
} }
void destruct()
{
content()->~ValueHolder();
}
Placeholder<ValueHolder> _valueHolder; Placeholder<ValueHolder> _valueHolder;

View File

@@ -74,19 +74,15 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! NOTE: Any/Dynamic::Var SOO will NOT work reliably !!! // !!! NOTE: Any/Dynamic::Var SOO will NOT work reliably !!!
// !!! without C++11 (std::aligned_storage in particular). !!! // !!! without C++11 (std::aligned_storage in particular). !!!
// !!! Only comment this out if your compiler has support !!!
// !!! for std::aligned_storage. !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// //
#ifndef POCO_ENABLE_SOO //#define POCO_NO_SOO
#define POCO_NO_SOO
#endif
// Small object size in bytes. When assigned to Any or Var, // Small object size in bytes. When assigned to Any or Var,
// objects larger than this value will be alocated on the heap, // objects larger than this value will be alocated on the heap,
// while those smaller will be placement new-ed into an // while those smaller will be placement new-ed into an
// internal buffer. // internal stack-auto-allocated buffer.
#if !defined(POCO_SMALL_OBJECT_SIZE) && !defined(POCO_NO_SOO) #if !defined(POCO_SMALL_OBJECT_SIZE) && !defined(POCO_NO_SOO)
#define POCO_SMALL_OBJECT_SIZE 32 #define POCO_SMALL_OBJECT_SIZE 32
#endif #endif

View File

@@ -621,10 +621,10 @@ private:
void destruct() void destruct()
{ {
if (!isEmpty()) delete content(); delete _pHolder;
} }
VarHolder* _pHolder; VarHolder* _pHolder = nullptr;
#else #else
@@ -633,53 +633,41 @@ private:
return _placeholder.content(); return _placeholder.content();
} }
void destruct()
{
}
template<typename ValueType,
typename std::enable_if<TypeSizeLE<VarHolderImpl<ValueType>, Placeholder<ValueType>::Size::value>::value>::type* = nullptr>
void constructSOO(const ValueType& value)
{
_placeholder.assignStack<VarHolderImpl<ValueType>, ValueType>(value);
}
template<typename ValueType,
typename std::enable_if<TypeSizeGT<VarHolderImpl<ValueType>, Placeholder<ValueType>::Size::value>::value>::type* = nullptr>
void constructSOO(const ValueType& value)
{
_placeholder.assignHeap<VarHolderImpl<ValueType>, ValueType>(value);
}
template<typename ValueType> template<typename ValueType>
void construct(const ValueType& value) void construct(const ValueType& value)
{ {
if (sizeof(VarHolderImpl<ValueType>) <= Placeholder<ValueType>::Size::value) constructSOO(value);
{
new (reinterpret_cast<VarHolder*>(_placeholder.holder)) VarHolderImpl<ValueType>(value);
_placeholder.setLocal(true);
}
else
{
_placeholder.pHolder = new VarHolderImpl<ValueType>(value);
_placeholder.setLocal(false);
}
} }
void construct(const char* value) void construct(const char* value)
{ {
std::string val(value); std::string val(value);
if (sizeof(VarHolderImpl<std::string>) <= Placeholder<std::string>::Size::value) constructSOO(val);
{
new (reinterpret_cast<VarHolder*>(_placeholder.holder)) VarHolderImpl<std::string>(val);
_placeholder.setLocal(true);
}
else
{
_placeholder.pHolder = new VarHolderImpl<std::string>(val);
_placeholder.setLocal(false);
}
} }
void construct(const Var& other) void construct(const Var& other)
{ {
_placeholder.erase();
if (!other.isEmpty()) if (!other.isEmpty())
other.content()->clone(&_placeholder); other.content()->clone(&_placeholder);
else
_placeholder.erase();
}
void destruct()
{
if (!isEmpty())
{
if (_placeholder.isLocal())
content()->~VarHolder();
else
delete content();
}
} }
Placeholder<VarHolder> _placeholder; Placeholder<VarHolder> _placeholder;
@@ -709,14 +697,13 @@ inline void Var::swap(Var& other)
if (!_placeholder.isLocal() && !other._placeholder.isLocal()) if (!_placeholder.isLocal() && !other._placeholder.isLocal())
{ {
std::swap(_placeholder.pHolder, other._placeholder.pHolder); _placeholder.swap(other._placeholder);
} }
else else
{ {
Var tmp(*this); Var tmp(*this);
try try
{ {
if (_placeholder.isLocal()) destruct();
construct(other); construct(other);
other = tmp; other = tmp;
} }

View File

@@ -295,31 +295,22 @@ protected:
template <typename T> template <typename T>
VarHolder* cloneHolder(Placeholder<VarHolder>* pVarHolder, const T& val) const VarHolder* cloneHolder(Placeholder<VarHolder>* pVarHolder, const T& val) const
/// Instantiates value holder wrapper. If size of the wrapper is /// Instantiates value holder wrapper.
/// larger than POCO_SMALL_OBJECT_SIZE, holder is instantiated on ///
/// Called from clone() member function of the implementation.
///
/// When the smal object optimization is enabled (POCO_NO_SOO not
/// defined), if size of the wrapper is larger than
/// POCO_SMALL_OBJECT_SIZE, holder is instantiated on
/// the heap, otherwise it is instantiated in-place (in the /// the heap, otherwise it is instantiated in-place (in the
/// pre-allocated buffer inside the holder). /// pre-allocated buffer inside the holder).
///
/// Called from clone() member function of the implementation when
/// small object optimization is enabled.
{ {
#ifdef POCO_NO_SOO #ifdef POCO_NO_SOO
(void)pVarHolder; (void)pVarHolder;
return new VarHolderImpl<T>(val); return new VarHolderImpl<T>(val);
#else #else
poco_check_ptr (pVarHolder); poco_check_ptr (pVarHolder);
if ((sizeof(VarHolderImpl<T>) <= Placeholder<T>::Size::value)) return makeSOOHolder(pVarHolder, val);
{
new ((VarHolder*) pVarHolder->holder) VarHolderImpl<T>(val);
pVarHolder->setLocal(true);
return (VarHolder*) pVarHolder->holder;
}
else
{
pVarHolder->pHolder = new VarHolderImpl<T>(val);
pVarHolder->setLocal(false);
return pVarHolder->pHolder;
}
#endif #endif
} }
@@ -420,6 +411,25 @@ protected:
} }
private: private:
#ifndef POCO_NO_SOO
template<typename T,
typename std::enable_if<TypeSizeLE<VarHolderImpl<T>, Placeholder<T>::Size::value>::value>::type* = nullptr>
VarHolder* makeSOOHolder(Placeholder<VarHolder>* pVarHolder, const T& val) const
{
poco_check_ptr (pVarHolder);
return pVarHolder->assignStack<VarHolderImpl<T>, T>(val);
}
template<typename T,
typename std::enable_if<TypeSizeGT<VarHolderImpl<T>, Placeholder<T>::Size::value>::value>::type* = nullptr>
VarHolder* makeSOOHolder(Placeholder<VarHolder>* pVarHolder, const T& val) const
{
poco_check_ptr (pVarHolder);
return pVarHolder->assignHeap<VarHolderImpl<T>, T>(val);
}
#endif
template <typename F, typename T> template <typename F, typename T>
void checkUpperLimit(const F& from) const void checkUpperLimit(const F& from) const
{ {

View File

@@ -47,7 +47,9 @@ Var::Var(const char* pVal)
Var::Var(const Var& other) Var::Var(const Var& other)
#ifdef POCO_NO_SOO #ifdef POCO_NO_SOO
: _pHolder(other._pHolder ? other._pHolder->clone() : 0) :_pHolder((this != &other) ?
(other._pHolder ? other._pHolder->clone() : nullptr) :
other._pHolder)
{ {
} }
#else #else
@@ -70,9 +72,10 @@ Var& Var::operator = (const Var& rhs)
Var tmp(rhs); Var tmp(rhs);
swap(tmp); swap(tmp);
#else #else
if ((this != &rhs) && !rhs.isEmpty()) if (this == &rhs) return *this;
if (!rhs.isEmpty())
construct(rhs); construct(rhs);
else if ((this != &rhs) && rhs.isEmpty()) else
_placeholder.erase(); _placeholder.erase();
#endif #endif
return *this; return *this;
@@ -330,8 +333,6 @@ void Var::empty()
delete _pHolder; delete _pHolder;
_pHolder = 0; _pHolder = 0;
#else #else
if (_placeholder.isLocal()) this->~Var();
else delete content();
_placeholder.erase(); _placeholder.erase();
#endif #endif
} }
@@ -343,8 +344,6 @@ void Var::clear()
delete _pHolder; delete _pHolder;
_pHolder = 0; _pHolder = 0;
#else #else
if (_placeholder.isLocal()) this->~Var();
else delete content();
_placeholder.erase(); _placeholder.erase();
#endif #endif
} }

View File

@@ -13,8 +13,10 @@
#include "CppUnit/TestSuite.h" #include "CppUnit/TestSuite.h"
#include "Poco/Exception.h" #include "Poco/Exception.h"
#include "Poco/Any.h" #include "Poco/Any.h"
#include "Poco/SharedPtr.h"
#include "Poco/Bugcheck.h" #include "Poco/Bugcheck.h"
#include <vector> #include <vector>
#include <memory>
#if defined(_MSC_VER) && _MSC_VER < 1400 #if defined(_MSC_VER) && _MSC_VER < 1400
@@ -272,6 +274,85 @@ void AnyTest::testVector()
} }
void AnyTest::testPlaceholder()
{
#ifndef POCO_NO_SOO
Placeholder<char> ph;
assertTrue(ph.isEmpty());
assertFalse(ph.isLocal());
char c = *ph.assignStack<char, char>(1);
assertTrue(1 == c);
assertFalse(ph.isEmpty());
assertTrue(ph.isLocal());
c = *ph.assignHeap<char, char>(2);
assertTrue(2 == c);
assertFalse(ph.isEmpty());
assertFalse(ph.isLocal());
ph.erase();
assertTrue(ph.isEmpty());
assertFalse(ph.isLocal());
if (sizeof(std::shared_ptr<int>) <= POCO_SMALL_OBJECT_SIZE)
{
Placeholder<std::shared_ptr<int>> sph;
assertTrue(sph.isEmpty());
assertFalse(sph.isLocal());
int i = **sph.assignStack<std::shared_ptr<int>, int*>(new int(1));
assertTrue(1 == i);
assertFalse(sph.isEmpty());
assertTrue(sph.isLocal());
i = **sph.assignHeap<std::shared_ptr<int>, int*>(new int(1));
assertTrue(1 == i);
assertFalse(sph.isEmpty());
assertFalse(sph.isLocal());
}
if (sizeof(Poco::SharedPtr<int>) <= POCO_SMALL_OBJECT_SIZE)
{
Placeholder<Poco::SharedPtr<int>> psph;
assertTrue(psph.isEmpty());
assertFalse(psph.isLocal());
int i = **psph.assignStack<Poco::SharedPtr<int>, int*>(new int(1));
assertTrue(1 == i);
assertFalse(psph.isEmpty());
assertTrue(psph.isLocal());
i = **psph.assignHeap<Poco::SharedPtr<int>, int*>(new int(1));
assertTrue(1 == i);
assertFalse(psph.isEmpty());
assertFalse(psph.isLocal());
}
if (sizeof(std::vector<int>) <= POCO_SMALL_OBJECT_SIZE)
{
Placeholder<std::vector<int>> vph;
assertTrue(vph.isEmpty());
assertFalse(vph.isLocal());
std::vector<int> inv{1,2,3};
std::vector<int> outv = *vph.assignStack<std::vector<int>, std::vector<int>>(inv);
assertTrue(inv == outv);
assertFalse(vph.isEmpty());
assertTrue(vph.isLocal());
outv.clear();
outv = *vph.assignHeap<std::vector<int>, std::vector<int>>(inv);
assertTrue(inv == outv);
assertFalse(vph.isEmpty());
assertFalse(vph.isLocal());
}
// ...
#endif
}
void AnyTest::setUp() void AnyTest::setUp()
{ {
} }
@@ -298,6 +379,7 @@ CppUnit::Test* AnyTest::suite()
CppUnit_addTest(pSuite, AnyTest, testInt); CppUnit_addTest(pSuite, AnyTest, testInt);
CppUnit_addTest(pSuite, AnyTest, testComplexType); CppUnit_addTest(pSuite, AnyTest, testComplexType);
CppUnit_addTest(pSuite, AnyTest, testVector); CppUnit_addTest(pSuite, AnyTest, testVector);
CppUnit_addTest(pSuite, AnyTest, testPlaceholder);
return pSuite; return pSuite;
} }

View File

@@ -37,6 +37,8 @@ public:
void testComplexType(); void testComplexType();
void testVector(); void testVector();
void testPlaceholder();
void setUp(); void setUp();
void tearDown(); void tearDown();
static CppUnit::Test* suite(); static CppUnit::Test* suite();

View File

@@ -29,6 +29,9 @@ using namespace Poco;
using namespace Poco::Dynamic; using namespace Poco::Dynamic;
namespace {
class Dummy class Dummy
{ {
public: public:
@@ -54,6 +57,8 @@ private:
int _val; int _val;
}; };
}
VarTest::VarTest(const std::string& rName): CppUnit::TestCase(rName) VarTest::VarTest(const std::string& rName): CppUnit::TestCase(rName)
{ {
@@ -3068,6 +3073,20 @@ void VarTest::testIterator()
} }
void VarTest::testSharedPtr()
{
Poco::SharedPtr<int> p = new int(42);
{
Var v;
v = p;
Var v1;
v = v1;
v1 = v;
}
assertTrue(p.referenceCount() == 1);
}
void VarTest::setUp() void VarTest::setUp()
{ {
} }
@@ -3116,6 +3135,7 @@ CppUnit::Test* VarTest::suite()
CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructString); CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructString);
CppUnit_addTest(pSuite, VarTest, testDynamicStructInt); CppUnit_addTest(pSuite, VarTest, testDynamicStructInt);
CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructInt); CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructInt);
CppUnit_addTest(pSuite, VarTest, testSharedPtr);
CppUnit_addTest(pSuite, VarTest, testArrayToString); CppUnit_addTest(pSuite, VarTest, testArrayToString);
CppUnit_addTest(pSuite, VarTest, testArrayToStringEscape); CppUnit_addTest(pSuite, VarTest, testArrayToStringEscape);
CppUnit_addTest(pSuite, VarTest, testStructToString); CppUnit_addTest(pSuite, VarTest, testStructToString);

View File

@@ -77,6 +77,7 @@ public:
void testUUID(); void testUUID();
void testEmpty(); void testEmpty();
void testIterator(); void testIterator();
void testSharedPtr();
void setUp(); void setUp();
void tearDown(); void tearDown();