Skip to content

Commit 0bdac7c

Browse files
committed
Fixed leaks if allocation of ctrl block fails
1 parent 2d077b7 commit 0bdac7c

File tree

2 files changed

+93
-11
lines changed

2 files changed

+93
-11
lines changed

include/oup/observable_unique_ptr.hpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -564,13 +564,19 @@ class basic_observable_ptr final {
564564
* \note Do *not* manually delete this raw pointer after the
565565
* @ref observable_unique_ptr is created. If possible, prefer
566566
* using @ref make_observable() instead of this constructor.
567+
* \note If the allocation of the control block fails, the
568+
* pointer `value` will be deleted, and no memory will leak.
567569
*/
568570
template<
569571
typename U,
570572
typename enable =
571573
std::enable_if_t<std::is_convertible_v<U*, T*> && queries::owner_allow_release()>>
572-
explicit basic_observable_ptr(U* value) :
573-
basic_observable_ptr(get_block_from_object_(value), value) {}
574+
explicit basic_observable_ptr(U* value) try :
575+
basic_observable_ptr(get_or_create_block_from_object_(value), value) {
576+
} catch (...) {
577+
// Allocation of control block failed, delete input pointer and rethrow
578+
ptr_deleter(value);
579+
}
574580

575581
/**
576582
* \brief Explicit ownership capture of a raw pointer, with customer deleter.
@@ -579,13 +585,19 @@ class basic_observable_ptr final {
579585
* \note Do *not* manually delete this raw pointer after the
580586
* @ref basic_observable_ptr is created. If possible, prefer
581587
* using @ref make_observable() instead of this constructor.
588+
* \note If the allocation of the control block fails, the
589+
* pointer `value` will be deleted, and no memory will leak.
582590
*/
583591
template<
584592
typename U,
585593
typename enable =
586594
std::enable_if_t<std::is_convertible_v<U*, T*> && queries::owner_allow_release()>>
587-
explicit basic_observable_ptr(U* value, Deleter del) :
588-
basic_observable_ptr(get_block_from_object_(value), value, std::move(del)) {}
595+
explicit basic_observable_ptr(U* value, Deleter del) try :
596+
basic_observable_ptr(get_or_create_block_from_object_(value), value, std::move(del)) {
597+
} catch (...) {
598+
// Allocation of control block failed, delete input pointer and rethrow
599+
del(value);
600+
}
589601

590602
/**
591603
* \brief Transfer ownership by implicit casting
@@ -666,9 +678,10 @@ class basic_observable_ptr final {
666678

667679
/**
668680
* \brief Replaces the managed object.
669-
* \param ptr The new object to manage (can be `nullptr`, then this is equivalent to
670-
* @ref reset())
681+
* \param ptr The new object to manage (can be null, then this is equivalent to @ref reset())
671682
* \note This function is enabled only if `Policy::is_sealed` is false.
683+
* \note If the allocation of the control block fails, the pointer `ptr` will be deleted, and
684+
* no memory will leak.
672685
*/
673686
template<
674687
typename U,
@@ -680,8 +693,14 @@ class basic_observable_ptr final {
680693
control_block_type* old_block = block;
681694

682695
// Assign the new one
683-
block = get_block_from_object_(ptr);
684-
ptr_deleter.data = ptr;
696+
try {
697+
block = get_or_create_block_from_object_(ptr);
698+
ptr_deleter.data = ptr;
699+
} catch (...) {
700+
// Allocation of control block failed, delete input pointer and rethrow
701+
ptr_deleter(ptr);
702+
throw;
703+
}
685704

686705
// Delete the old pointer
687706
// (this follows `std::unique_ptr` specs)

tests/runtime_tests.cpp

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
constexpr std::size_t max_allocations = 20'000;
1010
void* allocations[max_allocations];
1111
void* allocations_array[max_allocations];
12-
std::size_t num_allocations = 0u;
13-
std::size_t double_delete = 0u;
14-
bool memory_tracking = false;
12+
std::size_t num_allocations = 0u;
13+
std::size_t double_delete = 0u;
14+
bool memory_tracking = false;
15+
bool force_allocation_failure = false;
1516

1617
#if defined(OUP_PLATFORM_OSX)
1718
// Getting weird errors on MacOS when overriding operator new and delete,
@@ -27,6 +28,10 @@ void* allocate(std::size_t size, bool array) {
2728
throw std::bad_alloc();
2829
}
2930

31+
if (force_allocation_failure) {
32+
throw std::bad_alloc();
33+
}
34+
3035
void* p = std::malloc(size);
3136
if (!p) {
3237
throw std::bad_alloc();
@@ -277,6 +282,24 @@ TEST_CASE("owner acquiring constructor", "[owner_construction]") {
277282
REQUIRE(mem_track.double_del() == 0u);
278283
}
279284

285+
TEST_CASE("owner acquiring constructor bad alloc", "[owner_construction]") {
286+
memory_tracker mem_track;
287+
288+
{
289+
test_object* raw_ptr = new test_object;
290+
try {
291+
force_allocation_failure = true;
292+
test_ptr{raw_ptr};
293+
} catch (...) {
294+
}
295+
force_allocation_failure = false;
296+
}
297+
298+
REQUIRE(instances == 0);
299+
REQUIRE(mem_track.leaks() == 0u);
300+
REQUIRE(mem_track.double_del() == 0u);
301+
}
302+
280303
TEST_CASE("owner acquiring constructor with deleter", "[owner_construction]") {
281304
memory_tracker mem_track;
282305

@@ -294,6 +317,24 @@ TEST_CASE("owner acquiring constructor with deleter", "[owner_construction]") {
294317
REQUIRE(mem_track.double_del() == 0u);
295318
}
296319

320+
TEST_CASE("owner acquiring constructor with deleter bad alloc", "[owner_construction]") {
321+
memory_tracker mem_track;
322+
323+
{
324+
test_object* raw_ptr = new test_object;
325+
try {
326+
force_allocation_failure = true;
327+
test_ptr_with_deleter{raw_ptr, test_deleter{42}};
328+
} catch (...) {
329+
}
330+
force_allocation_failure = false;
331+
}
332+
333+
REQUIRE(instances == 0);
334+
REQUIRE(mem_track.leaks() == 0u);
335+
REQUIRE(mem_track.double_del() == 0u);
336+
}
337+
297338
TEST_CASE("owner acquiring constructor null", "[owner_construction]") {
298339
memory_tracker mem_track;
299340

@@ -1278,6 +1319,28 @@ TEST_CASE("owner reset to new", "[owner_utility]") {
12781319
REQUIRE(mem_track.double_del() == 0u);
12791320
}
12801321

1322+
TEST_CASE("owner reset to new bad alloc", "[owner_utility]") {
1323+
memory_tracker mem_track;
1324+
1325+
{
1326+
test_object* raw_ptr1 = new test_object;
1327+
test_object* raw_ptr2 = new test_object;
1328+
test_ptr ptr(raw_ptr1);
1329+
try {
1330+
force_allocation_failure = true;
1331+
ptr.reset(raw_ptr2);
1332+
} catch (...) {
1333+
}
1334+
force_allocation_failure = false;
1335+
REQUIRE(instances == 1);
1336+
REQUIRE(ptr.get() == raw_ptr1);
1337+
}
1338+
1339+
REQUIRE(instances == 0);
1340+
REQUIRE(mem_track.leaks() == 0u);
1341+
REQUIRE(mem_track.double_del() == 0u);
1342+
}
1343+
12811344
TEST_CASE("owner reset to new with deleter", "[owner_utility]") {
12821345
memory_tracker mem_track;
12831346

0 commit comments

Comments
 (0)