blob: 753c742c9d673cc54b244d07b66a6dc7f7e07014 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
#include <Eigen/SparseCore>
#include <memory>
#ifdef EIGEN_TEST_WRAP_MALLOC
static int allocations_before_failure = -1;
extern "C" void* real_malloc(std::size_t) asm("__real_malloc");
extern "C" void* real_realloc(void*, std::size_t) asm("__real_realloc");
extern "C" void* wrap_malloc(std::size_t) asm("__wrap_malloc");
extern "C" void* wrap_realloc(void*, std::size_t) asm("__wrap_realloc");
static bool fail_allocation() {
if (allocations_before_failure < 0) return false;
return allocations_before_failure-- == 0;
}
extern "C" void* wrap_malloc(std::size_t size) { return fail_allocation() ? nullptr : real_malloc(size); }
extern "C" void* wrap_realloc(void* ptr, std::size_t size) {
return fail_allocation() ? nullptr : real_realloc(ptr, size);
}
#endif
struct ReallocationScalar {
struct ConstructionFailure {};
static int live_objects;
static int live_resources;
static int defaults_before_throw;
static int moves_before_throw;
std::unique_ptr<int> value;
ReallocationScalar() {
#ifdef EIGEN_EXCEPTIONS
if (defaults_before_throw == 0) throw ConstructionFailure{};
#endif
if (defaults_before_throw > 0) --defaults_before_throw;
value.reset(new int(0));
++live_objects;
++live_resources;
}
ReallocationScalar(ReallocationScalar&& other) {
#ifdef EIGEN_EXCEPTIONS
if (moves_before_throw == 0) throw ConstructionFailure{};
#endif
if (moves_before_throw > 0) --moves_before_throw;
value = std::move(other.value);
++live_objects;
}
ReallocationScalar& operator=(const ReallocationScalar& other) {
*value = *other.value;
return *this;
}
~ReallocationScalar() {
if (value) --live_resources;
--live_objects;
}
static void reset_failures() {
defaults_before_throw = -1;
moves_before_throw = -1;
}
};
int ReallocationScalar::live_objects = 0;
int ReallocationScalar::live_resources = 0;
int ReallocationScalar::defaults_before_throw = -1;
int ReallocationScalar::moves_before_throw = -1;
template <bool Align>
void reallocation_success() {
using Scalar = ReallocationScalar;
Scalar::reset_failures();
Scalar* values = internal::conditional_aligned_new<Scalar, Align>(3);
for (int i = 0; i < 3; ++i) *values[i].value = i + 1;
for (int size : {5, 5, 2, 0}) {
const int old_size = Scalar::live_objects;
values = internal::conditional_aligned_realloc_new<Scalar, Align>(values, size, old_size);
VERIFY_IS_EQUAL(Scalar::live_objects, size);
VERIFY_IS_EQUAL(Scalar::live_resources, size);
for (int i = 0; i < size; ++i) VERIFY_IS_EQUAL(*values[i].value, i < 3 ? i + 1 : 0);
}
internal::conditional_aligned_delete<Scalar, Align>(values, 0);
}
#ifdef EIGEN_EXCEPTIONS
template <bool Align>
void reallocation_failure(bool fail_move, int successful_constructions, int old_size) {
using Scalar = ReallocationScalar;
Scalar::reset_failures();
Scalar* values = internal::conditional_aligned_new<Scalar, Align>(old_size);
for (int i = 0; i < old_size; ++i) *values[i].value = i + 1;
if (fail_move)
Scalar::moves_before_throw = successful_constructions;
else
Scalar::defaults_before_throw = successful_constructions;
bool caught = false;
try {
values = internal::conditional_aligned_realloc_new<Scalar, Align>(values, old_size + 2, old_size);
} catch (const Scalar::ConstructionFailure&) {
caught = true;
}
Scalar::reset_failures();
VERIFY(caught);
VERIFY_IS_EQUAL(Scalar::live_objects, old_size);
// Only a failing move can leave the original elements moved-from.
VERIFY_IS_EQUAL(Scalar::live_resources, fail_move ? old_size - successful_constructions : old_size);
if (!fail_move) {
for (int i = 0; i < old_size; ++i) VERIFY_IS_EQUAL(*values[i].value, i + 1);
}
internal::conditional_aligned_delete<Scalar, Align>(values, old_size);
VERIFY_IS_EQUAL(Scalar::live_objects, 0);
VERIFY_IS_EQUAL(Scalar::live_resources, 0);
}
void sparse_reserve_failure() {
using Scalar = ReallocationScalar;
for (int successful_defaults : {0, 1}) {
Scalar::reset_failures();
{
SparseVector<Scalar> vector(10);
vector.reserve(3);
VERIFY_IS_EQUAL(Scalar::live_objects, 3);
Scalar::defaults_before_throw = successful_defaults;
bool caught = false;
try {
vector.reserve(5);
} catch (const Scalar::ConstructionFailure&) {
caught = true;
}
Scalar::reset_failures();
VERIFY(caught);
VERIFY_IS_EQUAL(vector.size(), 10);
VERIFY_IS_EQUAL(vector.nonZeros(), 0);
VERIFY_IS_EQUAL(Scalar::live_objects, 3);
VERIFY_IS_EQUAL(Scalar::live_resources, 3);
}
VERIFY_IS_EQUAL(Scalar::live_objects, 0);
VERIFY_IS_EQUAL(Scalar::live_resources, 0);
}
}
#ifdef EIGEN_TEST_WRAP_MALLOC
void compressed_storage_trivial_allocation_failure() {
for (int operation : {0, 1, 2}) {
for (int successful_allocations : {0, 1}) {
internal::CompressedStorage<double, int> storage(3);
for (int i = 0; i < 3; ++i) {
storage.index(i) = 2 * i;
storage.value(i) = double(i + 1);
}
if (operation == 1) storage.reserve(2);
const Index capacity = storage.allocatedSize();
const auto update = [&]() {
if (operation == 0)
storage.reserve(2);
else if (operation == 1)
storage.squeeze();
else
storage.atWithInsertion(1, 7.0);
};
bool caught = false;
allocations_before_failure = successful_allocations;
try {
update();
} catch (const std::bad_alloc&) {
caught = true;
}
allocations_before_failure = -1;
VERIFY(caught);
VERIFY_IS_EQUAL(storage.size(), 3);
VERIFY_IS_EQUAL(storage.allocatedSize(), operation == 1 && successful_allocations == 1 ? 3 : capacity);
for (int i = 0; i < 3; ++i) {
VERIFY_IS_EQUAL(storage.index(i), 2 * i);
VERIFY_IS_EQUAL(storage.value(i), double(i + 1));
}
update();
VERIFY_IS_EQUAL(storage.size(), operation == 2 ? 4 : 3);
if (operation == 2) {
VERIFY_IS_EQUAL(storage.index(1), 1);
VERIFY_IS_EQUAL(storage.value(1), 7.0);
}
storage.reserve(4);
storage.squeeze();
}
}
}
void compressed_storage_allocation_failure() {
using Scalar = ReallocationScalar;
// Exercise reserve growth, squeeze shrinkage, and insertion growth.
for (int operation : {0, 1, 2}) {
for (int successful_allocations : {0, 1}) {
Scalar::reset_failures();
{
internal::CompressedStorage<Scalar, int> storage(3);
for (int i = 0; i < 3; ++i) {
storage.index(i) = 2 * i;
*storage.value(i).value = i + 1;
}
if (operation == 1) storage.reserve(2);
const Index capacity = storage.allocatedSize();
const Scalar* values = storage.valuePtr();
const int* indices = storage.indexPtr();
Scalar inserted;
*inserted.value = 7;
const auto update = [&]() {
if (operation == 0)
storage.reserve(2);
else if (operation == 1)
storage.squeeze();
else
storage.atWithInsertion(1, inserted);
};
bool caught = false;
allocations_before_failure = successful_allocations;
try {
update();
} catch (const std::bad_alloc&) {
caught = true;
}
allocations_before_failure = -1;
VERIFY(caught);
VERIFY_IS_EQUAL(storage.size(), 3);
VERIFY_IS_EQUAL(storage.allocatedSize(), capacity);
VERIFY(storage.valuePtr() == values);
VERIFY(storage.indexPtr() == indices);
VERIFY_IS_EQUAL(Scalar::live_objects, capacity + 1);
VERIFY_IS_EQUAL(Scalar::live_resources, capacity + 1);
for (int i = 0; i < 3; ++i) {
VERIFY_IS_EQUAL(storage.index(i), 2 * i);
VERIFY_IS_EQUAL(*storage.value(i).value, i + 1);
}
update();
VERIFY_IS_EQUAL(storage.size(), operation == 2 ? 4 : 3);
if (operation == 2) {
VERIFY_IS_EQUAL(storage.index(1), 1);
VERIFY_IS_EQUAL(*storage.value(1).value, 7);
}
storage.clear();
storage.squeeze();
VERIFY_IS_EQUAL(storage.allocatedSize(), 0);
VERIFY_IS_EQUAL(Scalar::live_objects, 1);
VERIFY_IS_EQUAL(Scalar::live_resources, 1);
}
VERIFY_IS_EQUAL(Scalar::live_objects, 0);
VERIFY_IS_EQUAL(Scalar::live_resources, 0);
}
}
}
#endif
#endif
EIGEN_DECLARE_TEST(memory_reallocation) {
CALL_SUBTEST(reallocation_success<false>());
CALL_SUBTEST(reallocation_success<true>());
#ifdef EIGEN_EXCEPTIONS
#ifdef EIGEN_TEST_WRAP_MALLOC
CALL_SUBTEST(compressed_storage_allocation_failure());
CALL_SUBTEST(compressed_storage_trivial_allocation_failure());
#endif
CALL_SUBTEST(sparse_reserve_failure());
for (int successful_constructions : {0, 1}) {
CALL_SUBTEST(reallocation_failure<false>(false, successful_constructions, 3));
CALL_SUBTEST(reallocation_failure<true>(false, successful_constructions, 3));
CALL_SUBTEST(reallocation_failure<false>(true, successful_constructions, 3));
CALL_SUBTEST(reallocation_failure<true>(true, successful_constructions, 3));
CALL_SUBTEST(reallocation_failure<false>(false, successful_constructions, 0));
CALL_SUBTEST(reallocation_failure<true>(false, successful_constructions, 0));
}
#endif
}