Skip to content

Commit 45a2f56

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 45a2f56

6 files changed

Lines changed: 200 additions & 26 deletions

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,35 @@ std::pair<T*, T*> setup()
211211
return {ptr1, ptr2};
212212
}
213213

214+
// Single-buffer setup for overlapping relocation tests.
215+
// Unlike setup<T>(), this only allocates one buffer, avoiding
216+
// the memory leak that occurs when the second buffer is unused.
217+
template <typename T>
218+
T* setup_single()
219+
{
220+
clear();
221+
222+
void* mem = std::malloc(N * sizeof(T));
223+
224+
HPX_TEST(mem);
225+
226+
T* ptr = static_cast<T*>(mem);
227+
228+
HPX_TEST(T::made.size() == 0);
229+
HPX_TEST(T::moved == 0);
230+
HPX_TEST(T::destroyed == 0);
231+
232+
for (int i = 0; i < N; i++)
233+
{
234+
hpx::construct_at(ptr + i, i);
235+
}
236+
237+
// N objects constructed
238+
HPX_TEST(T::made.size() == N);
239+
240+
return ptr;
241+
}
242+
214243
template <typename Ex>
215244
void test()
216245
{
@@ -377,7 +406,7 @@ void test_overlapping()
377406
static_assert(M + offset <= N);
378407

379408
{ // Overlapping trivially-relocatable
380-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
409+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
381410

382411
// Destroy the objects that will be overwritten for bookkeeping
383412
std::destroy(ptr, ptr + offset);
@@ -423,7 +452,7 @@ void test_overlapping()
423452
std::free(ptr);
424453
}
425454
{ // Overlapping non-trivially relocatable
426-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
455+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
427456

428457
// Destroy the objects that will be overwritten for bookkeeping purposes
429458
std::destroy(ptr, ptr + offset);
@@ -460,8 +489,8 @@ void test_overlapping()
460489
std::free(ptr);
461490
}
462491
{ // Overlapping non-trivially relocatable throwing
463-
auto [ptr, ___] =
464-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
492+
auto ptr =
493+
setup_single<non_trivially_relocatable_struct_throwing_overlapping>();
465494

466495
// Destroy the objects that will be overwritten for bookkeeping purposes
467496
std::destroy(ptr, ptr + offset);
@@ -530,7 +559,7 @@ void test_right_overlapping()
530559
static_assert(M + offset <= N);
531560

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

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

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,35 @@ std::pair<T*, T*> setup()
211211
return {ptr1, ptr2};
212212
}
213213

214+
// Single-buffer setup for overlapping relocation tests.
215+
// Unlike setup<T>(), this only allocates one buffer, avoiding
216+
// the memory leak that occurs when the second buffer is unused.
217+
template <typename T>
218+
T* setup_single()
219+
{
220+
clear();
221+
222+
void* mem = std::malloc(N * sizeof(T));
223+
224+
HPX_TEST(mem);
225+
226+
T* ptr = static_cast<T*>(mem);
227+
228+
HPX_TEST(T::made.size() == 0);
229+
HPX_TEST(T::moved == 0);
230+
HPX_TEST(T::destroyed == 0);
231+
232+
for (int i = 0; i < N; i++)
233+
{
234+
hpx::construct_at(ptr + i, i);
235+
}
236+
237+
// N objects constructed
238+
HPX_TEST(T::made.size() == N);
239+
240+
return ptr;
241+
}
242+
214243
template <typename Ex>
215244
void test()
216245
{
@@ -377,7 +406,7 @@ void test_overlapping()
377406
static_assert(M + offset <= N);
378407

379408
{ // Overlapping trivially-relocatable
380-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
409+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
381410

382411
// Destroy the objects that will be overwritten for bookkeeping
383412
std::destroy(ptr + M, ptr + M + offset);
@@ -422,7 +451,7 @@ void test_overlapping()
422451
std::free(ptr);
423452
}
424453
{ // Overlapping non-trivially relocatable
425-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
454+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
426455

427456
// Destroy the objects that will be overwritten for bookkeeping purposes
428457
std::destroy(ptr + M, ptr + M + offset);
@@ -458,8 +487,8 @@ void test_overlapping()
458487
std::free(ptr);
459488
}
460489
{ // Overlapping non-trivially relocatable throwing
461-
auto [ptr, ___] =
462-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
490+
auto ptr =
491+
setup_single<non_trivially_relocatable_struct_throwing_overlapping>();
463492

464493
// Destroy the objects that will be overwritten for bookkeeping purposes
465494
std::destroy(ptr + M, ptr + M + offset);

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,35 @@ std::pair<T*, T*> setup()
213213
return {ptr1, ptr2};
214214
}
215215

216+
// Single-buffer setup for overlapping relocation tests.
217+
// Unlike setup<T>(), this only allocates one buffer, avoiding
218+
// the memory leak that occurs when the second buffer is unused.
219+
template <typename T>
220+
T* setup_single()
221+
{
222+
clear();
223+
224+
void* mem = std::malloc(N * sizeof(T));
225+
226+
HPX_TEST(mem);
227+
228+
T* ptr = static_cast<T*>(mem);
229+
230+
HPX_TEST(T::made.size() == 0);
231+
HPX_TEST(T::moved == 0);
232+
HPX_TEST(T::destroyed == 0);
233+
234+
for (int i = 0; i < N; i++)
235+
{
236+
hpx::construct_at(ptr + i, i);
237+
}
238+
239+
// N objects constructed
240+
HPX_TEST(T::made.size() == N);
241+
242+
return ptr;
243+
}
244+
216245
template <typename LnPolicy, typename ExPolicy>
217246
void test(LnPolicy ln_policy, ExPolicy ex_policy)
218247
{
@@ -385,7 +414,7 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy ex_policy)
385414
static_assert(M + offset <= N);
386415

387416
{ // Overlapping trivially-relocatable
388-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
417+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
389418

390419
// Destroy the objects that will be overwritten for bookkeeping
391420
std::destroy(ptr + M, ptr + M + offset);
@@ -431,7 +460,7 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy ex_policy)
431460
std::free(ptr);
432461
}
433462
{ // Overlapping non-trivially relocatable
434-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
463+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
435464

436465
// Destroy the objects that will be overwritten for bookkeeping purposes
437466
std::destroy(ptr + M, ptr + M + offset);
@@ -468,8 +497,8 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy ex_policy)
468497
std::free(ptr);
469498
}
470499
{ // Overlapping non-trivially relocatable throwing
471-
auto [ptr, ___] =
472-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
500+
auto ptr =
501+
setup_single<non_trivially_relocatable_struct_throwing_overlapping>();
473502

474503
// Destroy the objects that will be overwritten for bookkeeping purposes
475504
std::destroy(ptr + M, ptr + M + offset);

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,35 @@ std::pair<T*, T*> setup()
212212
return {ptr1, ptr2};
213213
}
214214

215+
// Single-buffer setup for overlapping relocation tests.
216+
// Unlike setup<T>(), this only allocates one buffer, avoiding
217+
// the memory leak that occurs when the second buffer is unused.
218+
template <typename T>
219+
T* setup_single()
220+
{
221+
clear();
222+
223+
void* mem = std::malloc(N * sizeof(T));
224+
225+
HPX_TEST(mem);
226+
227+
T* ptr = static_cast<T*>(mem);
228+
229+
HPX_TEST(T::made.size() == 0);
230+
HPX_TEST(T::moved == 0);
231+
HPX_TEST(T::destroyed == 0);
232+
233+
for (int i = 0; i < N; i++)
234+
{
235+
hpx::construct_at(ptr + i, i);
236+
}
237+
238+
// N objects constructed
239+
HPX_TEST(T::made.size() == N);
240+
241+
return ptr;
242+
}
243+
215244
template <typename LnPolicy, typename ExPolicy>
216245
void test(LnPolicy ln_policy, ExPolicy&& ex_policy)
217246
{
@@ -384,7 +413,7 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy&& ex_policy)
384413
auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy));
385414

386415
{ // Overlapping trivially-relocatable
387-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
416+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
388417

389418
// Destroy the objects that will be overwritten for bookkeeping
390419
std::destroy(ptr, ptr + offset);
@@ -431,7 +460,7 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy&& ex_policy)
431460
std::free(ptr);
432461
}
433462
{ // Overlapping non-trivially relocatable
434-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
463+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
435464

436465
// Destroy the objects that will be overwritten for bookkeeping purposes
437466
std::destroy(ptr, ptr + offset);
@@ -469,8 +498,8 @@ void test_overlapping(LnPolicy ln_policy, ExPolicy&& ex_policy)
469498
std::free(ptr);
470499
}
471500
{ // Overlapping non-trivially relocatable throwing
472-
auto [ptr, ___] =
473-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
501+
auto ptr =
502+
setup_single<non_trivially_relocatable_struct_throwing_overlapping>();
474503

475504
// Destroy the objects that will be overwritten for bookkeeping purposes
476505
std::destroy(ptr, ptr + offset);

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,35 @@ std::pair<T*, T*> setup()
211211
return {ptr1, ptr2};
212212
}
213213

214+
// Single-buffer setup for overlapping relocation tests.
215+
// Unlike setup<T>(), this only allocates one buffer, avoiding
216+
// the memory leak that occurs when the second buffer is unused.
217+
template <typename T>
218+
T* setup_single()
219+
{
220+
clear();
221+
222+
void* mem = std::malloc(N * sizeof(T));
223+
224+
HPX_TEST(mem);
225+
226+
T* ptr = static_cast<T*>(mem);
227+
228+
HPX_TEST(T::made.size() == 0);
229+
HPX_TEST(T::moved == 0);
230+
HPX_TEST(T::destroyed == 0);
231+
232+
for (int i = 0; i < N; i++)
233+
{
234+
hpx::construct_at(ptr + i, i);
235+
}
236+
237+
// N objects constructed
238+
HPX_TEST(T::made.size() == N);
239+
240+
return ptr;
241+
}
242+
214243
template <typename Ex>
215244
void test()
216245
{
@@ -377,7 +406,7 @@ void test_overlapping()
377406
static_assert(M + offset <= N);
378407

379408
{ // Overlapping trivially-relocatable
380-
auto [ptr, ___] = setup<trivially_relocatable_struct_overlapping>();
409+
auto ptr = setup_single<trivially_relocatable_struct_overlapping>();
381410

382411
// Destroy the objects that will be overwritten for bookkeeping
383412
std::destroy(ptr, ptr + offset);
@@ -423,7 +452,7 @@ void test_overlapping()
423452
std::free(ptr);
424453
}
425454
{ // Overlapping non-trivially relocatable
426-
auto [ptr, ___] = setup<non_trivially_relocatable_struct_overlapping>();
455+
auto ptr = setup_single<non_trivially_relocatable_struct_overlapping>();
427456

428457
// Destroy the objects that will be overwritten for bookkeeping purposes
429458
std::destroy(ptr, ptr + offset);
@@ -460,8 +489,8 @@ void test_overlapping()
460489
std::free(ptr);
461490
}
462491
{ // Overlapping non-trivially relocatable throwing
463-
auto [ptr, ___] =
464-
setup<non_trivially_relocatable_struct_throwing_overlapping>();
492+
auto ptr =
493+
setup_single<non_trivially_relocatable_struct_throwing_overlapping>();
465494

466495
// Destroy the objects that will be overwritten for bookkeeping purposes
467496
std::destroy(ptr, ptr + offset);
@@ -530,7 +559,7 @@ void test_right_overlapping()
530559
static_assert(M + offset <= N);
531560

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

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

0 commit comments

Comments
 (0)