mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2025-01-13 15:13:42 +01:00
Detect memory leaks in tests (#2698)
This commit is contained in:
commit
220b60f315
@ -1,37 +1,18 @@
|
|||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
#include "malloc.h"
|
||||||
|
|
||||||
static void *sHeapStart;
|
static void *sHeapStart;
|
||||||
static u32 sHeapSize;
|
static u32 sHeapSize;
|
||||||
|
|
||||||
#define MALLOC_SYSTEM_ID 0xA3A3
|
|
||||||
|
|
||||||
struct MemBlock {
|
|
||||||
// Whether this block is currently allocated.
|
|
||||||
bool16 flag;
|
|
||||||
|
|
||||||
// Magic number used for error checking. Should equal MALLOC_SYSTEM_ID.
|
|
||||||
u16 magic;
|
|
||||||
|
|
||||||
// Size of the block (not including this header struct).
|
|
||||||
u32 size;
|
|
||||||
|
|
||||||
// Previous block pointer. Equals sHeapStart if this is the first block.
|
|
||||||
struct MemBlock *prev;
|
|
||||||
|
|
||||||
// Next block pointer. Equals sHeapStart if this is the last block.
|
|
||||||
struct MemBlock *next;
|
|
||||||
|
|
||||||
// Data in the memory block. (Arrays of length 0 are a GNU extension.)
|
|
||||||
u8 data[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
void PutMemBlockHeader(void *block, struct MemBlock *prev, struct MemBlock *next, u32 size)
|
void PutMemBlockHeader(void *block, struct MemBlock *prev, struct MemBlock *next, u32 size)
|
||||||
{
|
{
|
||||||
struct MemBlock *header = (struct MemBlock *)block;
|
struct MemBlock *header = (struct MemBlock *)block;
|
||||||
|
|
||||||
header->flag = FALSE;
|
header->allocated = FALSE;
|
||||||
|
header->locationHi = 0;
|
||||||
header->magic = MALLOC_SYSTEM_ID;
|
header->magic = MALLOC_SYSTEM_ID;
|
||||||
header->size = size;
|
header->size = size;
|
||||||
|
header->locationLo = 0;
|
||||||
header->prev = prev;
|
header->prev = prev;
|
||||||
header->next = next;
|
header->next = next;
|
||||||
}
|
}
|
||||||
@ -41,7 +22,7 @@ void PutFirstMemBlockHeader(void *block, u32 size)
|
|||||||
PutMemBlockHeader(block, (struct MemBlock *)block, (struct MemBlock *)block, size - sizeof(struct MemBlock));
|
PutMemBlockHeader(block, (struct MemBlock *)block, (struct MemBlock *)block, size - sizeof(struct MemBlock));
|
||||||
}
|
}
|
||||||
|
|
||||||
void *AllocInternal(void *heapStart, u32 size)
|
void *AllocInternal(void *heapStart, u32 size, const char *location)
|
||||||
{
|
{
|
||||||
struct MemBlock *pos = (struct MemBlock *)heapStart;
|
struct MemBlock *pos = (struct MemBlock *)heapStart;
|
||||||
struct MemBlock *head = pos;
|
struct MemBlock *head = pos;
|
||||||
@ -55,14 +36,14 @@ void *AllocInternal(void *heapStart, u32 size)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
// Loop through the blocks looking for unused block that's big enough.
|
// Loop through the blocks looking for unused block that's big enough.
|
||||||
|
|
||||||
if (!pos->flag) {
|
if (!pos->allocated) {
|
||||||
foundBlockSize = pos->size;
|
foundBlockSize = pos->size;
|
||||||
|
|
||||||
if (foundBlockSize >= size) {
|
if (foundBlockSize >= size) {
|
||||||
if (foundBlockSize - size < 2 * sizeof(struct MemBlock)) {
|
if (foundBlockSize - size < 2 * sizeof(struct MemBlock)) {
|
||||||
// The block isn't much bigger than the requested size,
|
// The block isn't much bigger than the requested size,
|
||||||
// so just use it.
|
// so just use it.
|
||||||
pos->flag = TRUE;
|
pos->allocated = TRUE;
|
||||||
} else {
|
} else {
|
||||||
// The block is significantly bigger than the requested
|
// The block is significantly bigger than the requested
|
||||||
// size, so split the rest into a separate block.
|
// size, so split the rest into a separate block.
|
||||||
@ -71,7 +52,7 @@ void *AllocInternal(void *heapStart, u32 size)
|
|||||||
|
|
||||||
splitBlock = (struct MemBlock *)(pos->data + size);
|
splitBlock = (struct MemBlock *)(pos->data + size);
|
||||||
|
|
||||||
pos->flag = TRUE;
|
pos->allocated = TRUE;
|
||||||
pos->size = size;
|
pos->size = size;
|
||||||
|
|
||||||
PutMemBlockHeader(splitBlock, pos, pos->next, foundBlockSize);
|
PutMemBlockHeader(splitBlock, pos, pos->next, foundBlockSize);
|
||||||
@ -82,6 +63,9 @@ void *AllocInternal(void *heapStart, u32 size)
|
|||||||
splitBlock->next->prev = splitBlock;
|
splitBlock->next->prev = splitBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pos->locationHi = ((uintptr_t)location) >> 14;
|
||||||
|
pos->locationLo = (uintptr_t)location;
|
||||||
|
|
||||||
return pos->data;
|
return pos->data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,12 +82,12 @@ void FreeInternal(void *heapStart, void *pointer)
|
|||||||
if (pointer) {
|
if (pointer) {
|
||||||
struct MemBlock *head = (struct MemBlock *)heapStart;
|
struct MemBlock *head = (struct MemBlock *)heapStart;
|
||||||
struct MemBlock *block = (struct MemBlock *)((u8 *)pointer - sizeof(struct MemBlock));
|
struct MemBlock *block = (struct MemBlock *)((u8 *)pointer - sizeof(struct MemBlock));
|
||||||
block->flag = FALSE;
|
block->allocated = FALSE;
|
||||||
|
|
||||||
// If the freed block isn't the last one, merge with the next block
|
// If the freed block isn't the last one, merge with the next block
|
||||||
// if it's not in use.
|
// if it's not in use.
|
||||||
if (block->next != head) {
|
if (block->next != head) {
|
||||||
if (!block->next->flag) {
|
if (!block->next->allocated) {
|
||||||
block->size += sizeof(struct MemBlock) + block->next->size;
|
block->size += sizeof(struct MemBlock) + block->next->size;
|
||||||
block->next->magic = 0;
|
block->next->magic = 0;
|
||||||
block->next = block->next->next;
|
block->next = block->next->next;
|
||||||
@ -115,7 +99,7 @@ void FreeInternal(void *heapStart, void *pointer)
|
|||||||
// If the freed block isn't the first one, merge with the previous block
|
// If the freed block isn't the first one, merge with the previous block
|
||||||
// if it's not in use.
|
// if it's not in use.
|
||||||
if (block != head) {
|
if (block != head) {
|
||||||
if (!block->prev->flag) {
|
if (!block->prev->allocated) {
|
||||||
block->prev->next = block->next;
|
block->prev->next = block->next;
|
||||||
|
|
||||||
if (block->next != head)
|
if (block->next != head)
|
||||||
@ -128,9 +112,9 @@ void FreeInternal(void *heapStart, void *pointer)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void *AllocZeroedInternal(void *heapStart, u32 size)
|
void *AllocZeroedInternal(void *heapStart, u32 size, const char *location)
|
||||||
{
|
{
|
||||||
void *mem = AllocInternal(heapStart, size);
|
void *mem = AllocInternal(heapStart, size, location);
|
||||||
|
|
||||||
if (mem != NULL) {
|
if (mem != NULL) {
|
||||||
if (size & 3)
|
if (size & 3)
|
||||||
@ -175,14 +159,14 @@ void InitHeap(void *heapStart, u32 heapSize)
|
|||||||
PutFirstMemBlockHeader(heapStart, heapSize);
|
PutFirstMemBlockHeader(heapStart, heapSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *Alloc(u32 size)
|
void *Alloc_(u32 size, const char *location)
|
||||||
{
|
{
|
||||||
return AllocInternal(sHeapStart, size);
|
return AllocInternal(sHeapStart, size, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *AllocZeroed(u32 size)
|
void *AllocZeroed_(u32 size, const char *location)
|
||||||
{
|
{
|
||||||
return AllocZeroedInternal(sHeapStart, size);
|
return AllocZeroedInternal(sHeapStart, size, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Free(void *pointer)
|
void Free(void *pointer)
|
||||||
@ -207,3 +191,16 @@ bool32 CheckHeap()
|
|||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const struct MemBlock *HeapHead(void)
|
||||||
|
{
|
||||||
|
return (const struct MemBlock *)sHeapStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *MemBlockLocation(const struct MemBlock *block)
|
||||||
|
{
|
||||||
|
if (!block->allocated)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return (const char *)(ROM_START | (block->locationHi << 14) | block->locationLo);
|
||||||
|
}
|
||||||
|
@ -11,11 +11,48 @@
|
|||||||
|
|
||||||
#define TRY_FREE_AND_SET_NULL(ptr) if (ptr != NULL) FREE_AND_SET_NULL(ptr)
|
#define TRY_FREE_AND_SET_NULL(ptr) if (ptr != NULL) FREE_AND_SET_NULL(ptr)
|
||||||
|
|
||||||
|
#define MALLOC_SYSTEM_ID 0xA3A3
|
||||||
|
|
||||||
|
struct MemBlock
|
||||||
|
{
|
||||||
|
// Whether this block is currently allocated.
|
||||||
|
u16 allocated:1;
|
||||||
|
|
||||||
|
u16 unused_00:4;
|
||||||
|
|
||||||
|
// High 11 bits of location pointer.
|
||||||
|
u16 locationHi:11;
|
||||||
|
|
||||||
|
// Magic number used for error checking. Should equal MALLOC_SYSTEM_ID.
|
||||||
|
u16 magic;
|
||||||
|
|
||||||
|
// Size of the block (not including this header struct).
|
||||||
|
u32 size:18;
|
||||||
|
|
||||||
|
// Low 14 bits of location pointer.
|
||||||
|
u32 locationLo:14;
|
||||||
|
|
||||||
|
// Previous block pointer. Equals sHeapStart if this is the first block.
|
||||||
|
struct MemBlock *prev;
|
||||||
|
|
||||||
|
// Next block pointer. Equals sHeapStart if this is the last block.
|
||||||
|
struct MemBlock *next;
|
||||||
|
|
||||||
|
// Data in the memory block. (Arrays of length 0 are a GNU extension.)
|
||||||
|
u8 data[0];
|
||||||
|
};
|
||||||
|
|
||||||
extern u8 gHeap[];
|
extern u8 gHeap[];
|
||||||
|
|
||||||
void *Alloc(u32 size);
|
#define Alloc(size) Alloc_(size, __FILE__ ":" STR(__LINE__))
|
||||||
void *AllocZeroed(u32 size);
|
#define AllocZeroed(size) AllocZeroed_(size, __FILE__ ":" STR(__LINE__))
|
||||||
|
|
||||||
|
void *Alloc_(u32 size, const char *location);
|
||||||
|
void *AllocZeroed_(u32 size, const char *location);
|
||||||
void Free(void *pointer);
|
void Free(void *pointer);
|
||||||
void InitHeap(void *pointer, u32 size);
|
void InitHeap(void *pointer, u32 size);
|
||||||
|
|
||||||
|
const struct MemBlock *HeapHead(void);
|
||||||
|
const char *MemBlockLocation(const struct MemBlock *block);
|
||||||
|
|
||||||
#endif // GUARD_ALLOC_H
|
#endif // GUARD_ALLOC_H
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
#define INTR_CHECK (*(u16 *)0x3007FF8)
|
#define INTR_CHECK (*(u16 *)0x3007FF8)
|
||||||
#define INTR_VECTOR (*(void **)0x3007FFC)
|
#define INTR_VECTOR (*(void **)0x3007FFC)
|
||||||
|
|
||||||
|
#define ROM_START 0x8000000
|
||||||
|
#define ROM_END 0xA000000
|
||||||
|
|
||||||
#define EWRAM_START 0x02000000
|
#define EWRAM_START 0x02000000
|
||||||
#define EWRAM_END (EWRAM_START + 0x40000)
|
#define EWRAM_END (EWRAM_START + 0x40000)
|
||||||
#define IWRAM_START 0x03000000
|
#define IWRAM_START 0x03000000
|
||||||
|
@ -147,6 +147,9 @@
|
|||||||
#define CAT(a, b) CAT_(a, b)
|
#define CAT(a, b) CAT_(a, b)
|
||||||
#define CAT_(a, b) a ## b
|
#define CAT_(a, b) a ## b
|
||||||
|
|
||||||
|
#define STR(a) STR_(a)
|
||||||
|
#define STR_(a) #a
|
||||||
|
|
||||||
// Converts a string to a compound literal, essentially making it a pointer to const u8
|
// Converts a string to a compound literal, essentially making it a pointer to const u8
|
||||||
#define COMPOUND_STRING(str) (const u8[]) _(str)
|
#define COMPOUND_STRING(str) (const u8[]) _(str)
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ struct TestRunnerState
|
|||||||
|
|
||||||
u8 result;
|
u8 result;
|
||||||
u8 expectedResult;
|
u8 expectedResult;
|
||||||
|
bool8 expectLeaks:1;
|
||||||
u32 timeoutSeconds;
|
u32 timeoutSeconds;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ extern struct TestRunnerState gTestRunnerState;
|
|||||||
void CB2_TestRunner(void);
|
void CB2_TestRunner(void);
|
||||||
|
|
||||||
void Test_ExpectedResult(enum TestResult);
|
void Test_ExpectedResult(enum TestResult);
|
||||||
|
void Test_ExpectLeaks(bool32);
|
||||||
void Test_ExitWithResult(enum TestResult, const char *fmt, ...);
|
void Test_ExitWithResult(enum TestResult, const char *fmt, ...);
|
||||||
|
|
||||||
s32 MgbaPrintf_(const char *fmt, ...);
|
s32 MgbaPrintf_(const char *fmt, ...);
|
||||||
@ -160,6 +162,9 @@ s32 MgbaPrintf_(const char *fmt, ...);
|
|||||||
#define KNOWN_FAILING \
|
#define KNOWN_FAILING \
|
||||||
Test_ExpectedResult(TEST_RESULT_FAIL)
|
Test_ExpectedResult(TEST_RESULT_FAIL)
|
||||||
|
|
||||||
|
#define KNOWN_LEAKING \
|
||||||
|
Test_ExpectLeaks(TRUE)
|
||||||
|
|
||||||
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
|
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
|
||||||
|
|
||||||
#define TO_DO \
|
#define TO_DO \
|
||||||
|
@ -614,6 +614,7 @@ struct BattleTestRunnerState
|
|||||||
bool8 runThen:1;
|
bool8 runThen:1;
|
||||||
bool8 runFinally:1;
|
bool8 runFinally:1;
|
||||||
bool8 runningFinally:1;
|
bool8 runningFinally:1;
|
||||||
|
bool8 tearDownBattle:1;
|
||||||
struct BattleTestData data;
|
struct BattleTestData data;
|
||||||
u8 *results;
|
u8 *results;
|
||||||
u8 checkProgressParameter;
|
u8 checkProgressParameter;
|
||||||
|
@ -111,6 +111,7 @@ void CB2_TestRunner(void)
|
|||||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||||
gTestRunnerState.result = TEST_RESULT_PASS;
|
gTestRunnerState.result = TEST_RESULT_PASS;
|
||||||
gTestRunnerState.expectedResult = TEST_RESULT_PASS;
|
gTestRunnerState.expectedResult = TEST_RESULT_PASS;
|
||||||
|
gTestRunnerState.expectLeaks = FALSE;
|
||||||
if (gTestRunnerHeadless)
|
if (gTestRunnerHeadless)
|
||||||
gTestRunnerState.timeoutSeconds = TIMEOUT_SECONDS;
|
gTestRunnerState.timeoutSeconds = TIMEOUT_SECONDS;
|
||||||
else
|
else
|
||||||
@ -141,6 +142,26 @@ void CB2_TestRunner(void)
|
|||||||
if (gTestRunnerState.test->runner->tearDown)
|
if (gTestRunnerState.test->runner->tearDown)
|
||||||
gTestRunnerState.test->runner->tearDown(gTestRunnerState.test->data);
|
gTestRunnerState.test->runner->tearDown(gTestRunnerState.test->data);
|
||||||
|
|
||||||
|
if (!gTestRunnerState.expectLeaks)
|
||||||
|
{
|
||||||
|
const struct MemBlock *head = HeapHead();
|
||||||
|
const struct MemBlock *block = head;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (block->allocated)
|
||||||
|
{
|
||||||
|
const char *location = MemBlockLocation(block);
|
||||||
|
if (location)
|
||||||
|
MgbaPrintf_("%s: %d bytes not freed", location, block->size);
|
||||||
|
else
|
||||||
|
MgbaPrintf_("<unknown>: %d bytes not freed", block->size);
|
||||||
|
gTestRunnerState.result = TEST_RESULT_FAIL;
|
||||||
|
}
|
||||||
|
block = block->next;
|
||||||
|
}
|
||||||
|
while (block != head);
|
||||||
|
}
|
||||||
|
|
||||||
if (gTestRunnerState.test->runner == &gAssumptionsRunner)
|
if (gTestRunnerState.test->runner == &gAssumptionsRunner)
|
||||||
{
|
{
|
||||||
if (gTestRunnerState.result != TEST_RESULT_PASS)
|
if (gTestRunnerState.result != TEST_RESULT_PASS)
|
||||||
@ -238,6 +259,11 @@ void Test_ExpectedResult(enum TestResult result)
|
|||||||
gTestRunnerState.expectedResult = result;
|
gTestRunnerState.expectedResult = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Test_ExpectLeaks(bool32 expectLeaks)
|
||||||
|
{
|
||||||
|
gTestRunnerState.expectLeaks = expectLeaks;
|
||||||
|
}
|
||||||
|
|
||||||
static void FunctionTest_SetUp(void *data)
|
static void FunctionTest_SetUp(void *data)
|
||||||
{
|
{
|
||||||
(void)data;
|
(void)data;
|
||||||
|
@ -893,6 +893,15 @@ static void BattleTest_TearDown(void *data)
|
|||||||
{
|
{
|
||||||
if (STATE)
|
if (STATE)
|
||||||
{
|
{
|
||||||
|
// Free resources that aren't cleaned up when the battle was
|
||||||
|
// aborted unexpectedly.
|
||||||
|
if (STATE->tearDownBattle)
|
||||||
|
{
|
||||||
|
FreeMonSpritesGfx();
|
||||||
|
FreeBattleSpritesData();
|
||||||
|
FreeBattleResources();
|
||||||
|
FreeAllWindowBuffers();
|
||||||
|
}
|
||||||
FREE_AND_SET_NULL(STATE->results);
|
FREE_AND_SET_NULL(STATE->results);
|
||||||
FREE_AND_SET_NULL(STATE);
|
FREE_AND_SET_NULL(STATE);
|
||||||
}
|
}
|
||||||
@ -923,6 +932,7 @@ static bool32 BattleTest_HandleExitWithResult(void *data, enum TestResult result
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
STATE->tearDownBattle = TRUE;
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user