Skip to content

Commit 718d1cf

Browse files
authored
DPL: Fix ownership of the content of Variant (#15939)
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 3019afb commit 718d1cf

3 files changed

Lines changed: 329 additions & 124 deletions

File tree

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

Lines changed: 24 additions & 18 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,22 @@ 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+
// Prevent constructing from a temporary vector to avoid double allocation
349+
template <typename T>
350+
Variant(std::vector<T>&&) = delete;
351+
345352
template <typename T>
346353
Variant(std::initializer_list<T>)
347354
{
@@ -355,8 +362,10 @@ class Variant
355362
~Variant();
356363
Variant& operator=(const Variant& other);
357364
Variant& operator=(Variant&& other) noexcept;
365+
// Assignment from a temporary vector has to be allowed because it is used, but copies
366+
// to make sure Variant owns its content
358367
template <typename T>
359-
Variant& operator=(std::vector<T>&& other) noexcept
368+
Variant& operator=(std::vector<T>&& other) // not noexcept because it allocates
360369
{
361370
*this = Variant(other);
362371
return *this;
@@ -381,31 +390,24 @@ class Variant
381390
return variant_helper<T>::get(&mStore);
382391
}
383392

393+
// The setters replace the current content, releasing it first.
384394
template <typename T>
385395
void set(T value)
386396
{
387-
return variant_helper<T>::set(&mStore, value);
397+
*this = Variant(value);
388398
}
389399

390400
template <typename T>
391401
void set(T value, size_t size)
392402
{
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());
403+
*this = Variant(value, size);
402404
}
403405

404406
template <typename T>
405407
void set(std::vector<T>& values)
406-
requires(std::is_same_v<T, std::string>)
408+
requires(std::is_pod_v<T> || std::is_same_v<T, std::string>)
407409
{
408-
return variant_helper<T*>::set(&mStore, values);
410+
*this = Variant(values);
409411
}
410412

411413
[[nodiscard]] VariantType type() const { return mType; }
@@ -414,6 +416,10 @@ class Variant
414416

415417
private:
416418
friend std::ostream& operator<<(std::ostream& oss, Variant const& val);
419+
// Helpers to manage the store depending on the actual content
420+
void copyStore(Variant const& other);
421+
void moveStore(Variant& other) noexcept;
422+
void destroyStore() noexcept;
417423
using storage_t = std::aligned_union<8, int, int8_t, int16_t, int64_t,
418424
uint8_t, uint16_t, uint32_t, uint64_t,
419425
const char*, float, double, bool,

0 commit comments

Comments
 (0)