Skip to content

Commit 09809b6

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 108314a commit 09809b6

3 files changed

Lines changed: 221 additions & 117 deletions

File tree

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

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ struct variant_helper<std::string> {
317317
class Variant
318318
{
319319
public:
320-
Variant(VariantType type = VariantType::Unknown) : mType{type} {}
320+
Variant(VariantType type = VariantType::Unknown);
321321

322322
template <typename T>
323323
Variant(T value) : mType{variant_trait_v<T>}
@@ -381,31 +381,25 @@ class Variant
381381
return variant_helper<T>::get(&mStore);
382382
}
383383

384+
// The setters replace the current content, releasing it first.
384385
template <typename T>
385386
void set(T value)
386387
{
387-
return variant_helper<T>::set(&mStore, value);
388+
*this = Variant(value);
388389
}
389390

390391
template <typename T>
391392
void set(T value, size_t size)
392393
{
393-
mSize = size;
394-
return variant_helper<T>::set(&mStore, value, mSize);
394+
*this = Variant(value, size);
395395
}
396396

397+
/// FIXME: set for vector of strings is not used anywhere, why?
397398
template <typename T>
398399
void set(std::vector<T>& values)
399-
requires(std::is_pod_v<T>)
400+
requires(std::is_pod_v<T> || std::is_same_v<T, std::string>)
400401
{
401-
return variant_helper<T*>::set(&mStore, values.data(), values.size());
402-
}
403-
404-
template <typename T>
405-
void set(std::vector<T>& values)
406-
requires(std::is_same_v<T, std::string>)
407-
{
408-
return variant_helper<T*>::set(&mStore, values);
402+
*this = Variant(values);
409403
}
410404

411405
[[nodiscard]] VariantType type() const { return mType; }
@@ -414,6 +408,10 @@ class Variant
414408

415409
private:
416410
friend std::ostream& operator<<(std::ostream& oss, Variant const& val);
411+
// Helpers to manage the store depending on the actual content
412+
void copyStore(Variant const& other);
413+
void moveStore(Variant& other) noexcept;
414+
void destroyStore() noexcept;
417415
using storage_t = std::aligned_union<8, int, int8_t, int16_t, int64_t,
418416
uint8_t, uint16_t, uint32_t, uint64_t,
419417
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)