309 lines
12 KiB
C++
309 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2017-2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
*/
|
|
|
|
#include "runtime/helpers/options.h"
|
|
#include "unit_tests/memory_leak_listener.h"
|
|
#include "unit_tests/fixtures/memory_management_fixture.h"
|
|
#include "unit_tests/helpers/memory_management.h"
|
|
#include <cinttypes>
|
|
#if defined(__linux__)
|
|
#include <cstdio>
|
|
#include <execinfo.h>
|
|
#include <cxxabi.h>
|
|
#include <dlfcn.h>
|
|
#elif defined(_WIN32)
|
|
#include <Windows.h>
|
|
#pragma warning(push) // Saves the current warning state.
|
|
#pragma warning(disable : 4091) // Temporarily disables warning 4091.
|
|
#include <DbgHelp.h>
|
|
#pragma warning(pop) // Restores the warning state.
|
|
#pragma comment(lib, "Dbghelp.lib")
|
|
#endif
|
|
|
|
namespace Os {
|
|
extern const char *frontEndDllName;
|
|
extern const char *igcDllName;
|
|
} // namespace Os
|
|
|
|
bool printMemoryOpCallStack = true;
|
|
|
|
void MemoryManagementFixture::SetUp() {
|
|
EXPECT_EQ(static_cast<size_t>(-1), MemoryManagement::failingAllocation);
|
|
MemoryManagement::indexAllocation = 0;
|
|
MemoryManagement::indexDeallocation = 0;
|
|
MemoryManagement::failingAllocation = -1;
|
|
previousAllocations = MemoryManagement::numAllocations.load();
|
|
MemoryManagement::logTraces = OCLRT::captureCallStacks;
|
|
}
|
|
|
|
void MemoryManagementFixture::TearDown() {
|
|
clearFailingAllocation();
|
|
checkForLeaks();
|
|
MemoryManagement::logTraces = false;
|
|
}
|
|
|
|
void MemoryManagementFixture::setFailingAllocation(size_t allocation) {
|
|
MemoryManagement::indexAllocation = 0;
|
|
MemoryManagement::failingAllocation = allocation;
|
|
}
|
|
|
|
void MemoryManagementFixture::clearFailingAllocation() {
|
|
MemoryManagement::failingAllocation = -1;
|
|
}
|
|
|
|
size_t MemoryManagementFixture::enumerateLeak(size_t indexAllocationTop, size_t indexDeallocationTop, bool lookFromBack, bool requireCallStack, bool fastLookup) {
|
|
using MemoryManagement::AllocationEvent;
|
|
using MemoryManagement::eventsAllocated;
|
|
using MemoryManagement::eventsDeallocated;
|
|
|
|
static auto start = invalidLeakIndex;
|
|
auto newIndex = start == invalidLeakIndex ? 0 : start;
|
|
bool potentialLeak = false;
|
|
auto potentialLeakIndex = newIndex;
|
|
|
|
for (; newIndex < indexAllocationTop; ++newIndex) {
|
|
auto currentIndex = lookFromBack ? indexAllocationTop - newIndex - 1 : newIndex;
|
|
auto &eventAllocation = eventsAllocated[currentIndex];
|
|
|
|
if (requireCallStack && eventAllocation.frames == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (fastLookup && eventAllocation.fastLeakDetectionMode == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (eventAllocation.event != AllocationEvent::EVENT_UNKNOWN) {
|
|
// Should be some sort of allocation
|
|
size_t deleteIndex = 0;
|
|
for (; deleteIndex < indexDeallocationTop; ++deleteIndex) {
|
|
auto &eventDeallocation = eventsDeallocated[deleteIndex];
|
|
|
|
if (eventDeallocation.address == eventAllocation.address &&
|
|
eventDeallocation.event != AllocationEvent::EVENT_UNKNOWN) {
|
|
|
|
//this memory was once freed, now it is allocated but not freed
|
|
if (requireCallStack && eventDeallocation.frames == 0) {
|
|
potentialLeak = true;
|
|
potentialLeakIndex = currentIndex;
|
|
continue;
|
|
}
|
|
|
|
//allocated with fast lookup, but deallocated other way, not a match
|
|
if (fastLookup && eventDeallocation.fastLeakDetectionMode != 1) {
|
|
continue;
|
|
}
|
|
|
|
// Clear the NEW and DELETE event.
|
|
eventAllocation.event = AllocationEvent::EVENT_UNKNOWN;
|
|
eventDeallocation.event = AllocationEvent::EVENT_UNKNOWN;
|
|
potentialLeak = false;
|
|
// Found a corresponding match
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (potentialLeak) {
|
|
return potentialLeakIndex;
|
|
}
|
|
|
|
if (deleteIndex == indexDeallocationTop) {
|
|
start = newIndex + 1;
|
|
return currentIndex;
|
|
}
|
|
}
|
|
}
|
|
start = invalidLeakIndex;
|
|
return start;
|
|
}
|
|
|
|
std::string printCallStack(const MemoryManagement::AllocationEvent &event) {
|
|
std::string result = "";
|
|
|
|
printf("printCallStack.%d.%d\n", printMemoryOpCallStack, event.frames);
|
|
if (!OCLRT::captureCallStacks) {
|
|
printf("for detailed stack information turn on captureCallStacks in memory_management_fixture.h\n");
|
|
}
|
|
if (printMemoryOpCallStack && event.frames > 0) {
|
|
#if defined(__linux__)
|
|
char **bt = backtrace_symbols(event.callstack, event.frames);
|
|
char *demangled;
|
|
int status;
|
|
char output[1024];
|
|
Dl_info info;
|
|
result += "\n";
|
|
for (int i = 0; i < event.frames; ++i) {
|
|
dladdr(event.callstack[i], &info);
|
|
if (info.dli_sname) {
|
|
demangled = nullptr;
|
|
status = -1;
|
|
if (info.dli_sname[0] == '_') {
|
|
demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status);
|
|
}
|
|
snprintf(output, sizeof(output), "%-3d %*p %s + %zd\n",
|
|
(event.frames - i - 1), (int)(sizeof(void *) + 2), event.callstack[i],
|
|
status == 0 ? demangled : info.dli_sname == 0 ? bt[i] : info.dli_sname,
|
|
(char *)event.callstack[i] - (char *)info.dli_saddr);
|
|
free(demangled);
|
|
} else {
|
|
snprintf(output, sizeof(output), "%-3d %*p %s\n",
|
|
(event.frames - i - 1), (int)(sizeof(void *) + 2), event.callstack[i], bt[i]);
|
|
}
|
|
result += std::string(output);
|
|
}
|
|
result += "\n";
|
|
free(bt);
|
|
#elif defined(_WIN32)
|
|
SYMBOL_INFO *symbol;
|
|
HANDLE process;
|
|
process = GetCurrentProcess();
|
|
SymInitialize(process, NULL, TRUE);
|
|
|
|
symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
|
|
symbol->MaxNameLen = 255;
|
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
|
|
for (int i = 0; i < event.frames; i++) {
|
|
SymFromAddr(process, (DWORD64)(event.callstack[i]), 0, symbol);
|
|
|
|
printf("%i: %s - 0x%0" PRIx64 "\n", event.frames - i - 1, symbol->Name, symbol->Address);
|
|
}
|
|
|
|
free(symbol);
|
|
#endif
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
::testing::AssertionResult MemoryManagementFixture::assertLeak(
|
|
const char *leakExpr,
|
|
size_t leakIndex) {
|
|
using MemoryManagement::AllocationEvent;
|
|
using MemoryManagement::eventsAllocated;
|
|
|
|
if (leakIndex == invalidLeakIndex) {
|
|
return ::testing::AssertionSuccess();
|
|
}
|
|
auto &event = eventsAllocated[leakIndex];
|
|
|
|
switch (event.event) {
|
|
case AllocationEvent::EVENT_DELETE:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "]: delete doesn't have corresponding new. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_DELETE_ARRAY:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "]: delete[] doesn't have corresponding new[]. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_NEW:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "]: new doesn't have corresponding delete. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_NEW_NOTHROW:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "]: new (std::nothrow) doesn't have corresponding delete. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_NEW_ARRAY:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "]: new [] doesn't have corresponding delete[]. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_NEW_ARRAY_NOTHROW:
|
|
return ::testing::AssertionFailure() << "event[" << leakIndex
|
|
<< "] new (std::nothrow) [] doesn't have corresponding delete[]. allocation address="
|
|
<< event.address
|
|
<< ", allocation size=" << event.size << printCallStack(event);
|
|
break;
|
|
case AllocationEvent::EVENT_NEW_ARRAY_NOTHROW_FAIL:
|
|
case AllocationEvent::EVENT_NEW_ARRAY_FAIL:
|
|
case AllocationEvent::EVENT_NEW_NOTHROW_FAIL:
|
|
case AllocationEvent::EVENT_NEW_FAIL:
|
|
case AllocationEvent::EVENT_UNKNOWN:
|
|
default:
|
|
return ::testing::AssertionFailure() << "Unknown event[" << leakIndex << "] detected. allocation size=" << event.size;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MemoryManagementFixture::checkForLeaks() {
|
|
// We have to alias MemoryManagement::numAllocations because
|
|
// the following EXPECT_EQ actually allocates more memory :-)
|
|
auto currentAllocations = MemoryManagement::numAllocations.load();
|
|
auto indexAllocationTop = MemoryManagement::indexAllocation.load();
|
|
auto indexDellocationTop = MemoryManagement::indexDeallocation.load();
|
|
if (previousAllocations != currentAllocations) {
|
|
auto testInfo = ::testing::UnitTest::GetInstance()->current_test_info();
|
|
auto testResult = testInfo->result();
|
|
if (testResult->Passed()) {
|
|
//EXPECT_EQ(previousAllocations, currentAllocations);
|
|
size_t leakEventIndex;
|
|
do {
|
|
leakEventIndex = enumerateLeak(indexAllocationTop, indexDellocationTop);
|
|
EXPECT_PRED_FORMAT1(assertLeak, leakEventIndex);
|
|
auto invalidLeakIndexValues = invalidLeakIndex;
|
|
EXPECT_EQ(leakEventIndex, invalidLeakIndexValues);
|
|
} while (leakEventIndex != invalidLeakIndex);
|
|
} else {
|
|
printf("*** WARNING: Leaks found but dumping disabled during test failure ***\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemoryManagementFixture::injectFailures(InjectedFunction &method, uint32_t maxIndex) {
|
|
MemoryManagement::indexAllocation = 0;
|
|
method(-1);
|
|
auto numCurrentAllocations = MemoryManagement::indexAllocation.load();
|
|
|
|
for (auto i = 0u; i < numCurrentAllocations; i++) {
|
|
// Force a failure
|
|
MemoryManagement::indexAllocation = numCurrentAllocations;
|
|
MemoryManagement::failingAllocation = i + numCurrentAllocations;
|
|
if (MemoryManagement::eventsAllocated[i].event == MemoryManagement::AllocationEvent::EVENT_NEW ||
|
|
MemoryManagement::eventsAllocated[i].event == MemoryManagement::AllocationEvent::EVENT_NEW_ARRAY) {
|
|
|
|
continue;
|
|
}
|
|
|
|
if (maxIndex != 0 && i > maxIndex) {
|
|
break;
|
|
}
|
|
|
|
// Call the method under test
|
|
method(i);
|
|
|
|
// Restore allocations
|
|
MemoryManagement::failingAllocation = -1;
|
|
}
|
|
MemoryManagement::failingAllocation = -1;
|
|
}
|
|
|
|
void MemoryManagementFixture::injectFailureOnIndex(InjectedFunction &method, uint32_t index) {
|
|
MemoryManagement::indexAllocation = 0;
|
|
method(-1);
|
|
auto numCurrentAllocations = MemoryManagement::indexAllocation.load();
|
|
|
|
// Force a failure
|
|
MemoryManagement::indexAllocation = numCurrentAllocations;
|
|
MemoryManagement::failingAllocation = index + numCurrentAllocations;
|
|
|
|
// Call the method under test
|
|
method(index);
|
|
|
|
// Restore allocations
|
|
MemoryManagement::failingAllocation = -1;
|
|
}
|