Skip to content

Commit dd6b670

Browse files
Fix memory leak in overlapping relocation test functions (#7184)
The setup<T>() helper allocates two buffers (mem1, mem2), but overlapping test blocks only use one buffer for in-place relocation. The second buffer was captured as a discarded binding (___) and never freed, leaking N * sizeof(T) bytes per overlapping test block. This commit introduces a setup_single<T>() helper that allocates only one buffer, and updates all overlapping test blocks across 6 files to use it: - uninitialized_relocate.cpp (4 sites) - uninitialized_relocaten.cpp (4 sites) - uninitialized_relocate_backward.cpp (3 sites) - uninitialized_relocate_sender.cpp (3 sites) - uninitialized_relocaten_sender.cpp (3 sites) - uninitialized_relocate_backward_sender.cpp (3 sites) Closes #7184
1 parent 6656c71 commit dd6b670

7 files changed

Lines changed: 302 additions & 1203 deletions

libs/core/algorithms/tests/unit/algorithms/uninitialized_relocate.cpp

Lines changed: 7 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -10,207 +10,13 @@
1010
#include <hpx/modules/testing.hpp>
1111
#include <hpx/modules/type_support.hpp>
1212

13-
#include <atomic>
13+
#include "uninitialized_relocate_test_utils.hpp"
14+
1415
#include <cstddef>
15-
#include <random>
16-
#include <set>
1716
#include <type_traits>
18-
#include <utility>
19-
20-
constexpr int N = 500; // number of objects to construct
21-
constexpr int M = 200; // number of objects to relocate
22-
constexpr int K = 100; // number of objects to relocate before throwing
2317

24-
static_assert(N > M);
25-
static_assert(M > K);
26-
27-
using hpx::experimental::is_trivially_relocatable_v;
2818
using hpx::experimental::uninitialized_relocate;
2919

30-
std::mutex m;
31-
32-
template <typename F>
33-
void simple_mutex_operation(F&& f)
34-
{
35-
std::lock_guard<std::mutex> lk(m);
36-
f();
37-
}
38-
39-
// enum for the different types of objects
40-
enum relocation_category
41-
{
42-
trivially_relocatable,
43-
non_trivially_relocatable,
44-
non_trivially_relocatable_throwing
45-
};
46-
47-
template <relocation_category c, bool overlapping_test = false>
48-
struct counted_struct
49-
{
50-
static std::set<counted_struct<c, overlapping_test>*> made;
51-
static std::atomic<int> moved;
52-
static std::atomic<int> destroyed;
53-
int data;
54-
55-
explicit counted_struct(int data)
56-
: data(data)
57-
{
58-
// Check that we are not constructing an object on top of another
59-
simple_mutex_operation([&]() {
60-
HPX_TEST(!made.count(this));
61-
made.insert(this);
62-
});
63-
}
64-
65-
counted_struct(counted_struct&& other) noexcept(
66-
c != non_trivially_relocatable_throwing)
67-
: data(other.data)
68-
{
69-
if constexpr (c == non_trivially_relocatable_throwing)
70-
{
71-
if (moved++ == K - 1)
72-
{
73-
throw 42;
74-
}
75-
}
76-
else
77-
{
78-
moved++;
79-
}
80-
81-
// Check that we are not constructing an object on top of another
82-
simple_mutex_operation([&]() {
83-
// Unless we are testing overlapping relocation
84-
// we should not be move-constructing an object on top of another
85-
if constexpr (!overlapping_test)
86-
{
87-
HPX_TEST(!made.count(this));
88-
}
89-
made.insert(this);
90-
});
91-
}
92-
93-
~counted_struct()
94-
{
95-
destroyed++;
96-
97-
// Check that the object was constructed
98-
// and not already destroyed
99-
simple_mutex_operation([&]() {
100-
HPX_TEST(made.count(this));
101-
made.erase(this);
102-
});
103-
}
104-
105-
// making sure the address is never directly accessed
106-
friend void operator&(counted_struct) = delete;
107-
};
108-
109-
template <relocation_category c, bool overlapping_test>
110-
std::set<counted_struct<c, overlapping_test>*>
111-
counted_struct<c, overlapping_test>::made;
112-
113-
template <relocation_category c, bool overlapping_test>
114-
std::atomic<int> counted_struct<c, overlapping_test>::moved = 0;
115-
116-
template <relocation_category c, bool overlapping_test>
117-
std::atomic<int> counted_struct<c, overlapping_test>::destroyed = 0;
118-
119-
// Non overlapping relocation testing mechanisms
120-
using trivially_relocatable_struct = counted_struct<trivially_relocatable>;
121-
HPX_DECLARE_TRIVIALLY_RELOCATABLE(trivially_relocatable_struct);
122-
123-
using non_trivially_relocatable_struct =
124-
counted_struct<non_trivially_relocatable>;
125-
126-
using non_trivially_relocatable_struct_throwing =
127-
counted_struct<non_trivially_relocatable_throwing>;
128-
129-
// Overlapping relocation testing mechanisms
130-
using trivially_relocatable_struct_overlapping =
131-
counted_struct<trivially_relocatable, true>;
132-
HPX_DECLARE_TRIVIALLY_RELOCATABLE(trivially_relocatable_struct_overlapping);
133-
134-
using non_trivially_relocatable_struct_overlapping =
135-
counted_struct<non_trivially_relocatable, true>;
136-
137-
using non_trivially_relocatable_struct_throwing_overlapping =
138-
counted_struct<non_trivially_relocatable_throwing, true>;
139-
140-
// Check that the correct types are trivially relocatable
141-
static_assert(is_trivially_relocatable_v<trivially_relocatable_struct>);
142-
static_assert(
143-
is_trivially_relocatable_v<trivially_relocatable_struct_overlapping>);
144-
145-
static_assert(!is_trivially_relocatable_v<non_trivially_relocatable_struct>);
146-
static_assert(
147-
!is_trivially_relocatable_v<non_trivially_relocatable_struct_overlapping>);
148-
149-
static_assert(
150-
!is_trivially_relocatable_v<non_trivially_relocatable_struct_throwing>);
151-
static_assert(!is_trivially_relocatable_v<
152-
non_trivially_relocatable_struct_throwing_overlapping>);
153-
154-
void clear()
155-
{
156-
// Reset for the next test
157-
trivially_relocatable_struct::moved = 0;
158-
trivially_relocatable_struct::destroyed = 0;
159-
trivially_relocatable_struct::made.clear();
160-
161-
trivially_relocatable_struct_overlapping::moved = 0;
162-
trivially_relocatable_struct_overlapping::destroyed = 0;
163-
trivially_relocatable_struct_overlapping::made.clear();
164-
165-
non_trivially_relocatable_struct::moved = 0;
166-
non_trivially_relocatable_struct::destroyed = 0;
167-
non_trivially_relocatable_struct::made.clear();
168-
169-
non_trivially_relocatable_struct_overlapping::moved = 0;
170-
non_trivially_relocatable_struct_overlapping::destroyed = 0;
171-
non_trivially_relocatable_struct_overlapping::made.clear();
172-
173-
non_trivially_relocatable_struct_throwing::moved = 0;
174-
non_trivially_relocatable_struct_throwing::destroyed = 0;
175-
non_trivially_relocatable_struct_throwing::made.clear();
176-
177-
non_trivially_relocatable_struct_throwing_overlapping::moved = 0;
178-
non_trivially_relocatable_struct_throwing_overlapping::destroyed = 0;
179-
non_trivially_relocatable_struct_throwing_overlapping::made.clear();
180-
}
181-
182-
template <typename T>
183-
std::pair<T*, T*> setup()
184-
{
185-
clear();
186-
187-
void* mem1 = std::malloc(N * sizeof(T));
188-
void* mem2 = std::malloc(N * sizeof(T));
189-
190-
HPX_TEST(mem1 && mem2);
191-
192-
T* ptr1 = static_cast<T*>(mem1);
193-
T* ptr2 = static_cast<T*>(mem2);
194-
195-
HPX_TEST(T::made.size() == 0);
196-
HPX_TEST(T::moved == 0);
197-
HPX_TEST(T::destroyed == 0);
198-
199-
for (int i = 0; i < N; i++)
200-
{
201-
hpx::construct_at(ptr1 + i, i);
202-
}
203-
204-
// fill ptr2 with 0 after M
205-
std::fill(static_cast<std::byte*>(mem2) + M * sizeof(T),
206-
static_cast<std::byte*>(mem2) + N * sizeof(T), std::byte{0});
207-
208-
// N objects constructed
209-
HPX_TEST(T::made.size() == N);
210-
211-
return {ptr1, ptr2};
212-
}
213-
21420
template <typename Ex>
21521
void test()
21622
{
@@ -377,7 +183,7 @@ void test_overlapping()
377183
static_assert(M + offset <= N);
378184

379185
{ // Overlapping trivially-relocatable
380-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
186+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
381187

382188
// Destroy the objects that will be overwritten for bookkeeping
383189
std::destroy(ptr, ptr + offset);
@@ -423,7 +229,7 @@ void test_overlapping()
423229
std::free(ptr);
424230
}
425231
{ // Overlapping non-trivially relocatable
426-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
232+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
427233

428234
// Destroy the objects that will be overwritten for bookkeeping purposes
429235
std::destroy(ptr, ptr + offset);
@@ -460,8 +266,8 @@ void test_overlapping()
460266
std::free(ptr);
461267
}
462268
{ // Overlapping non-trivially relocatable throwing
463-
auto [ptr, ___] =
464-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
269+
auto ptr = setup_single<
270+
non_trivially_relocatable_struct_throwing_overlapping>();
465271

466272
// Destroy the objects that will be overwritten for bookkeeping purposes
467273
std::destroy(ptr, ptr + offset);
@@ -530,7 +336,7 @@ void test_right_overlapping()
530336
static_assert(M + offset <= N);
531337

532338
{ // Right-overlapping trivially-relocatable
533-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
339+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
534340

535341
// Destroy the objects in [M, M + offset) that will be overwritten
536342
// by the destination tail extending beyond the source range

0 commit comments

Comments
 (0)