Skip to content

Commit eff3ac1

Browse files
committed
Fix ownership of the content of Variant
Copy and move of Variants holding Array2D, LabeledArray or string array values copied the storage byte by byte, so copies shared the same heap buffers, and the destructor never destroyed these objects, leaking them. Assignments never released the previous content, move assignment of strings and arrays duplicated the buffer and dropped the original one, and move assignment of string arrays corrupted the source vector.
1 parent 56b38d0 commit eff3ac1

3 files changed

Lines changed: 319 additions & 123 deletions

File tree

‎Framework/Core/include/Framework/Variant.h‎

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "Framework/RuntimeError.h"
1515
#include "Framework/Array2D.h"
16+
#include "Framework/Traits.h"
1617
#include <type_traits>
1718
#include <cstring>
1819
#include <cstdint>
@@ -261,9 +262,9 @@ struct variant_helper {
261262
new (reinterpret_cast<T*>(store)) T{};
262263
*(reinterpret_cast<T*>(store)) = value;
263264
}
264-
static void set(void* store, T values, size_t size)
265+
static void set(void* store, std::remove_pointer_t<T> const* values, size_t size)
265266
{
266-
*reinterpret_cast<T*>(store) = reinterpret_cast<T>(std::memcpy(std::malloc(size * sizeof(std::remove_pointer_t<T>)), reinterpret_cast<void*>(values), size * sizeof(std::remove_pointer_t<T>)));
267+
*reinterpret_cast<T*>(store) = reinterpret_cast<T>(std::memcpy(std::malloc(size * sizeof(std::remove_pointer_t<T>)), reinterpret_cast<void const*>(values), size * sizeof(std::remove_pointer_t<T>)));
267268
}
268269

269270
static T get(const void* store) { return *(reinterpret_cast<const T*>(store)); }
@@ -317,9 +318,10 @@ struct variant_helper<std::string> {
317318
class Variant
318319
{
319320
public:
320-
Variant(VariantType type = VariantType::Unknown) : mType{type} {}
321+
Variant(VariantType type = VariantType::Unknown);
321322

322323
template <typename T>
324+
requires(!is_specialization_v<T, std::vector>)
323325
Variant(T value) : mType{variant_trait_v<T>}
324326
{
325327
variant_helper<decltype(value)>::set(&mStore, value);
@@ -331,17 +333,23 @@ class Variant
331333
variant_helper<T>::set(&mStore, values, mSize);
332334
}
333335

336+
// A Variant owns what it stores: the content of vectors is copied
334337
template <typename T>
335-
Variant(std::vector<T>& values) : mType{variant_trait_v<T*>}, mSize{values.size()}
338+
Variant(std::vector<T> const& values) : mType{variant_trait_v<T*>}, mSize{values.size()}
336339
{
337340
variant_helper<T*>::set(&mStore, values.data(), mSize);
338341
}
339342

340-
Variant(std::vector<std::string>& values) : mType{VariantType::ArrayString}, mSize{values.size()}
343+
Variant(std::vector<std::string> const& values) : mType{VariantType::ArrayString}, mSize{values.size()}
341344
{
342345
variant_helper<std::vector<std::string>>::set(&mStore, values);
343346
}
344347

348+
// A temporary vector cannot hand over its buffer to a Variant,
349+
// so creating one only to copy it is an error
350+
template <typename T>
351+
Variant(std::vector<T>&&) = delete;
352+
345353
template <typename T>
346354
Variant(std::initializer_list<T>)
347355
{
@@ -355,6 +363,8 @@ class Variant
355363
~Variant();
356364
Variant& operator=(const Variant& other);
357365
Variant& operator=(Variant&& other) noexcept;
366+
// Assignment from a temporary vector has to be allowed because it is used, but copies
367+
// to make sure Variant owns its content
358368
template <typename T>
359369
Variant& operator=(std::vector<T>&& other) noexcept
360370
{
@@ -381,31 +391,25 @@ class Variant
381391
return variant_helper<T>::get(&mStore);
382392
}
383393

394+
// The setters replace the current content, releasing it first.
384395
template <typename T>
385396
void set(T value)
386397
{
387-
return variant_helper<T>::set(&mStore, value);
398+
*this = Variant(value);
388399
}
389400

390401
template <typename T>
391402
void set(T value, size_t size)
392403
{
393-
mSize = size;
394-
return variant_helper<T>::set(&mStore, value, mSize);
395-
}
396-
397-
template <typename T>
398-
void set(std::vector<T>& values)
399-
requires(std::is_pod_v<T>)
400-
{
401-
return variant_helper<T*>::set(&mStore, values.data(), values.size());
404+
*this = Variant(value, size);
402405
}
403406

407+
/// FIXME: set for vector of strings is not used anywhere, why?
404408
template <typename T>
405409
void set(std::vector<T>& values)
406-
requires(std::is_same_v<T, std::string>)
410+
requires(std::is_pod_v<T> || std::is_same_v<T, std::string>)
407411
{
408-
return variant_helper<T*>::set(&mStore, values);
412+
*this = Variant(values);
409413
}
410414

411415
[[nodiscard]] VariantType type() const { return mType; }
@@ -414,6 +418,10 @@ class Variant
414418

415419
private:
416420
friend std::ostream& operator<<(std::ostream& oss, Variant const& val);
421+
// Helpers to manage the store depending on the actual content
422+
void copyStore(Variant const& other);
423+
void moveStore(Variant& other) noexcept;
424+
void destroyStore() noexcept;
417425
using storage_t = std::aligned_union<8, int, int8_t, int16_t, int64_t,
418426
uint8_t, uint16_t, uint32_t, uint64_t,
419427
const char*, float, double, bool,

‎Framework/Core/src/Variant.cxx‎

Lines changed: 128 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
#include "Framework/Variant.h"
1212
#include "Framework/VariantPropertyTreeHelpers.h"
1313
#include "Framework/VariantJSONHelpers.h"
14+
#include <cstdlib>
15+
#include <cstring>
1416
#include <iostream>
17+
#include <memory>
1518
#include <sstream>
1619

1720
namespace o2::framework
@@ -84,145 +87,166 @@ std::string Variant::asString() const
8487
return ss.str();
8588
}
8689

87-
Variant::Variant(const Variant& other) : mType(other.mType)
90+
namespace
8891
{
89-
// In case this is an array we need to duplicate it to avoid
90-
// double deletion.
91-
switch (mType) {
92-
case VariantType::String:
93-
mSize = other.mSize;
94-
variant_helper<const char*>::set(&mStore, other.get<const char*>());
95-
return;
96-
case VariantType::ArrayInt:
97-
mSize = other.mSize;
98-
variant_helper<int*>::set(&mStore, other.get<int*>(), mSize);
99-
return;
100-
case VariantType::ArrayFloat:
101-
mSize = other.mSize;
102-
variant_helper<float*>::set(&mStore, other.get<float*>(), mSize);
103-
return;
104-
case VariantType::ArrayDouble:
105-
mSize = other.mSize;
106-
variant_helper<double*>::set(&mStore, other.get<double*>(), mSize);
107-
return;
108-
case VariantType::ArrayBool:
109-
mSize = other.mSize;
110-
variant_helper<bool*>::set(&mStore, other.get<bool*>(), mSize);
111-
return;
92+
/// Helper visitor for Variant
93+
template <typename F>
94+
bool visitStoredObject(VariantType type, F&& f)
95+
{
96+
switch (type) {
11297
case VariantType::ArrayString:
113-
mSize = other.mSize;
114-
variant_helper<std::vector<std::string>>::set(&mStore, other.get<std::vector<std::string>>());
115-
return;
98+
f.template operator()<std::vector<std::string>>();
99+
return true;
100+
case VariantType::Array2DInt:
101+
f.template operator()<Array2D<int>>();
102+
return true;
103+
case VariantType::Array2DFloat:
104+
f.template operator()<Array2D<float>>();
105+
return true;
106+
case VariantType::Array2DDouble:
107+
f.template operator()<Array2D<double>>();
108+
return true;
109+
case VariantType::LabeledArrayInt:
110+
f.template operator()<LabeledArray<int>>();
111+
return true;
112+
case VariantType::LabeledArrayFloat:
113+
f.template operator()<LabeledArray<float>>();
114+
return true;
115+
case VariantType::LabeledArrayDouble:
116+
f.template operator()<LabeledArray<double>>();
117+
return true;
118+
case VariantType::LabeledArrayString:
119+
f.template operator()<LabeledArray<std::string>>();
120+
return true;
116121
default:
117-
mStore = other.mStore;
118-
mSize = other.mSize;
122+
return false;
119123
}
120124
}
121125

122-
Variant::Variant(Variant&& other) noexcept : mType(other.mType)
126+
/// Types for which the storage keeps a pointer to a manually allocated buffer
127+
bool holdsMallocedPointer(VariantType type)
123128
{
124-
mStore = other.mStore;
125-
mSize = other.mSize;
126-
switch (mType) {
129+
switch (type) {
127130
case VariantType::String:
128131
case VariantType::ArrayInt:
129132
case VariantType::ArrayFloat:
130133
case VariantType::ArrayDouble:
131134
case VariantType::ArrayBool:
132-
case VariantType::ArrayString:
133-
*reinterpret_cast<void**>(&(other.mStore)) = nullptr;
134-
return;
135+
return true;
135136
default:
136-
return;
137+
return false;
137138
}
138139
}
139140

140-
Variant::~Variant()
141+
template <typename T>
142+
T* copyBuffer(T const* values, size_t size)
143+
{
144+
if (values == nullptr) {
145+
return nullptr;
146+
}
147+
return reinterpret_cast<T*>(std::memcpy(std::malloc(size * sizeof(T)), values, size * sizeof(T)));
148+
}
149+
} // namespace
150+
151+
Variant::Variant(VariantType type) : mType{type}
152+
{
153+
// Make sure that destroying a Variant created without a value is always safe
154+
// by creating a default stored object upfront
155+
if (!visitStoredObject(mType, [this]<typename T>() { new (&mStore) T{}; })) {
156+
std::memset(&mStore, 0, sizeof(mStore));
157+
}
158+
}
159+
160+
void Variant::copyStore(Variant const& other)
141161
{
142-
// In case we allocated an array, we
143-
// should delete it.
162+
// Proper objects are simply copied
163+
if (visitStoredObject(mType, [this, &other]<typename T>() { new (&mStore) T(*reinterpret_cast<T const*>(&other.mStore)); })) {
164+
return;
165+
}
166+
// Manually allocated buffers have to be managed
144167
switch (mType) {
145-
case VariantType::String:
168+
case VariantType::String: {
169+
auto const* value = *reinterpret_cast<char const* const*>(&other.mStore);
170+
*reinterpret_cast<char**>(&mStore) = value != nullptr ? strdup(value) : nullptr;
171+
return;
172+
}
146173
case VariantType::ArrayInt:
174+
*reinterpret_cast<int**>(&mStore) = copyBuffer(*reinterpret_cast<int* const*>(&other.mStore), mSize);
175+
return;
147176
case VariantType::ArrayFloat:
177+
*reinterpret_cast<float**>(&mStore) = copyBuffer(*reinterpret_cast<float* const*>(&other.mStore), mSize);
178+
return;
148179
case VariantType::ArrayDouble:
149-
case VariantType::ArrayBool: {
150-
if (reinterpret_cast<void**>(&mStore) != nullptr) {
151-
free(*reinterpret_cast<void**>(&mStore));
152-
}
180+
*reinterpret_cast<double**>(&mStore) = copyBuffer(*reinterpret_cast<double* const*>(&other.mStore), mSize);
153181
return;
154-
}
155-
case VariantType::ArrayString: {
156-
// Allocated with placement new. Nothing to delete.
182+
case VariantType::ArrayBool:
183+
*reinterpret_cast<bool**>(&mStore) = copyBuffer(*reinterpret_cast<bool* const*>(&other.mStore), mSize);
157184
return;
158-
}
159185
default:
160-
return;
186+
// Trivially copyable content
187+
mStore = other.mStore;
188+
}
189+
}
190+
191+
void Variant::moveStore(Variant& other) noexcept
192+
{
193+
// Correct move for objects, leaving proper "moved from" state
194+
if (visitStoredObject(mType, [this, &other]<typename T>() { new (&mStore) T(std::move(*reinterpret_cast<T*>(&other.mStore))); })) {
195+
return;
196+
}
197+
mStore = other.mStore;
198+
// Buffers have to change their owner
199+
if (holdsMallocedPointer(mType)) {
200+
*reinterpret_cast<void**>(&other.mStore) = nullptr;
201+
}
202+
}
203+
204+
void Variant::destroyStore() noexcept
205+
{
206+
// destroy objects
207+
if (visitStoredObject(mType, [this]<typename T>() { std::destroy_at(reinterpret_cast<T*>(&mStore)); })) {
208+
return;
209+
}
210+
// deallocate buffers
211+
if (holdsMallocedPointer(mType)) {
212+
free(*reinterpret_cast<void**>(&mStore));
161213
}
162214
}
163215

216+
Variant::Variant(const Variant& other) : mType(other.mType), mSize(other.mSize)
217+
{
218+
copyStore(other);
219+
}
220+
221+
Variant::Variant(Variant&& other) noexcept : mType(other.mType), mSize(other.mSize)
222+
{
223+
moveStore(other);
224+
}
225+
226+
Variant::~Variant()
227+
{
228+
destroyStore();
229+
}
230+
164231
Variant& Variant::operator=(const Variant& other)
165232
{
166-
mSize = other.mSize;
167-
mType = other.mType;
168-
switch (mType) {
169-
case VariantType::String:
170-
variant_helper<const char*>::set(&mStore, other.get<const char*>());
171-
return *this;
172-
case VariantType::ArrayInt:
173-
variant_helper<int*>::set(&mStore, other.get<int*>(), mSize);
174-
return *this;
175-
case VariantType::ArrayFloat:
176-
variant_helper<float*>::set(&mStore, other.get<float*>(), mSize);
177-
return *this;
178-
case VariantType::ArrayDouble:
179-
variant_helper<double*>::set(&mStore, other.get<double*>(), mSize);
180-
return *this;
181-
case VariantType::ArrayBool:
182-
variant_helper<bool*>::set(&mStore, other.get<bool*>(), mSize);
183-
return *this;
184-
case VariantType::ArrayString:
185-
variant_helper<std::vector<std::string>>::set(&mStore, other.get<std::vector<std::string>>());
186-
return *this;
187-
default:
188-
mStore = other.mStore;
189-
return *this;
233+
if (this != &other) {
234+
// Copy first, so that a throwing copy leaves this Variant untouched
235+
Variant copy(other);
236+
*this = std::move(copy);
190237
}
238+
return *this;
191239
}
192240

193241
Variant& Variant::operator=(Variant&& other) noexcept
194242
{
195-
mSize = other.mSize;
196-
mType = other.mType;
197-
switch (mType) {
198-
case VariantType::String:
199-
variant_helper<const char*>::set(&mStore, other.get<const char*>());
200-
*reinterpret_cast<char**>(&(other.mStore)) = nullptr;
201-
return *this;
202-
case VariantType::ArrayInt:
203-
variant_helper<int*>::set(&mStore, other.get<int*>(), mSize);
204-
*reinterpret_cast<int**>(&(other.mStore)) = nullptr;
205-
return *this;
206-
case VariantType::ArrayFloat:
207-
variant_helper<float*>::set(&mStore, other.get<float*>(), mSize);
208-
*reinterpret_cast<float**>(&(other.mStore)) = nullptr;
209-
return *this;
210-
case VariantType::ArrayDouble:
211-
variant_helper<double*>::set(&mStore, other.get<double*>(), mSize);
212-
*reinterpret_cast<double**>(&(other.mStore)) = nullptr;
213-
return *this;
214-
case VariantType::ArrayBool:
215-
variant_helper<bool*>::set(&mStore, other.get<bool*>(), mSize);
216-
*reinterpret_cast<bool**>(&(other.mStore)) = nullptr;
217-
return *this;
218-
case VariantType::ArrayString:
219-
variant_helper<std::vector<std::string>>::set(&mStore, other.get<std::vector<std::string>>());
220-
*reinterpret_cast<std::vector<std::string>**>(&(other.mStore)) = nullptr;
221-
return *this;
222-
default:
223-
mStore = other.mStore;
224-
return *this;
243+
if (this != &other) {
244+
destroyStore();
245+
mType = other.mType;
246+
mSize = other.mSize;
247+
moveStore(other);
225248
}
249+
return *this;
226250
}
227251

228252
std::pair<std::vector<std::string>, std::vector<std::string>> extractLabels(boost::property_tree::ptree const& tree)

0 commit comments

Comments
 (0)