1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-02-21 19:48:29 +02:00

Audit mem contexts returned from functions into the calling context.

It is possible for functions to accidentally leak child contexts into the calling context, which may use a lot of memory depending on the use case and where it happens.

Use the function return type to determine what should be returned and error when something else is returned. Add FUNCTION_AUDIT_*() macros to handle exceptions.

This checking is only performed during unit tests on the code being covered by the specific unit test.

Note that this does not work yet for memory allocations, i.e. memNew(). These are pretty rare so are not as much of an issue and they can be added in the future.
This commit is contained in:
David Steele 2023-01-12 17:36:57 +07:00
parent de1dfb66ca
commit 9ca492cecf
79 changed files with 603 additions and 70 deletions

View File

@ -28,6 +28,8 @@ archiveGetFile(
FUNCTION_LOG_PARAM(STRING, walDestination);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(request != NULL);
ASSERT(actualList != NULL && !lstEmpty(actualList));
ASSERT(walDestination != NULL);

View File

@ -363,6 +363,8 @@ archiveGetCheck(const StringList *archiveRequestList)
FUNCTION_LOG_PARAM(STRING_LIST, archiveRequestList);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(archiveRequestList != NULL);
ASSERT(!strLstEmpty(archiveRequestList));

View File

@ -109,6 +109,8 @@ archivePushFile(
FUNCTION_LOG_PARAM(STRING_LIST, priorErrorList);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(walSource != NULL);
ASSERT(archiveFile != NULL);
ASSERT(repoList != NULL);

View File

@ -218,6 +218,8 @@ archivePushCheck(bool pgPathSet)
FUNCTION_LOG_PARAM(BOOL, pgPathSet);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ArchivePushCheckResult result = {.repoList = lstNewP(sizeof(ArchivePushFileRepoData)), .errorList = strLstNew()};
MEM_CONTEXT_TEMP_BEGIN()

View File

@ -169,6 +169,8 @@ backupInit(const InfoBackup *infoBackup)
FUNCTION_LOG_PARAM(INFO_BACKUP, infoBackup);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(infoBackup != NULL);
// Initialize for offline backup
@ -817,6 +819,8 @@ backupStart(BackupData *backupData)
FUNCTION_LOG_PARAM(BACKUP_DATA, backupData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
BackupStartResult result = {.lsn = NULL};
MEM_CONTEXT_TEMP_BEGIN()
@ -1011,6 +1015,8 @@ backupStop(BackupData *backupData, Manifest *manifest)
FUNCTION_LOG_PARAM(MANIFEST, manifest);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
BackupStopResult result = {.lsn = NULL};
if (cfgOptionBool(cfgOptOnline))
@ -1059,6 +1065,8 @@ backupJobResultPageChecksumOut(VariantList *const result, const unsigned int pag
FUNCTION_TEST_PARAM(UINT, pageEnd);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
// Output a single page
if (pageBegin == pageEnd)
{
@ -1083,6 +1091,8 @@ backupJobResultPageChecksum(PackRead *const checksumPageResult)
FUNCTION_LOG_PARAM(PACK_READ, checksumPageResult);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
VariantList *result = NULL;
// If there is an error result array
@ -1509,6 +1519,8 @@ backupProcessQueue(const BackupData *const backupData, Manifest *const manifest,
FUNCTION_LOG_PARAM_P(VOID, jobData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(manifest != NULL);
uint64_t result = 0;

View File

@ -237,7 +237,7 @@ pageChecksumNew(const unsigned int segmentNo, const unsigned int segmentPageTota
OBJ_NEW_BEGIN(PageChecksum, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
PageChecksum *driver = OBJ_NEW_ALLOC();
PageChecksum *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::PageChecksum);
*driver = (PageChecksum)
{

View File

@ -110,6 +110,8 @@ checkPrimary(const DbGetResult dbGroup)
FUNCTION_LOG_PARAM(DB_GET_RESULT, dbGroup);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
// If a primary is defined, check the configuration and perform a WAL switch and make sure the WAL is archived
if (dbGroup.primary != NULL)
{

View File

@ -306,6 +306,8 @@ archiveDbList(
FUNCTION_TEST_PARAM(UINT, repoKey);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(stanza != NULL);
ASSERT(pgData != NULL);
ASSERT(archiveSection != NULL);
@ -397,6 +399,8 @@ backupListAdd(
FUNCTION_TEST_PARAM(INFO_REPO_DATA, repoData); // The repo data where this backup is located
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(backupSection != NULL);
ASSERT(backupData != NULL);
ASSERT(repoData != NULL);
@ -586,6 +590,8 @@ backupList(
FUNCTION_TEST_PARAM(UINT, repoIdxMax); // The index of the last repo to check
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(backupSection != NULL);
ASSERT(stanzaData != NULL);
@ -658,6 +664,8 @@ stanzaInfoList(List *stanzaRepoList, const String *const backupLabel, const unsi
FUNCTION_TEST_PARAM(UINT, repoIdxMax);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(stanzaRepoList != NULL);
VariantList *result = varLstNew();
@ -804,6 +812,8 @@ formatTextBackup(const DbGroup *dbGroup, String *resultStr)
FUNCTION_TEST_PARAM(STRING, resultStr);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(dbGroup != NULL);
strCatFmt(resultStr, "\n wal archive min/max (%s): ", strZ(dbGroup->version));
@ -1002,6 +1012,8 @@ formatTextDb(
FUNCTION_TEST_PARAM(UINT64, currentPgSystemId);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(stanzaInfo != NULL);
ASSERT(currentPgVersion != NULL);
@ -1162,6 +1174,8 @@ infoUpdateStanza(
FUNCTION_TEST_PARAM(STRING, backupLabel);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(storage != NULL);
ASSERT(stanzaRepo != NULL);

View File

@ -28,6 +28,8 @@ storageListRenderInfo(const StorageInfo *const info, IoWrite *const write, const
FUNCTION_TEST_PARAM(BOOL, json);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(info != NULL);
ASSERT(write != NULL);
@ -84,6 +86,8 @@ storageListRender(IoWrite *write)
FUNCTION_LOG_PARAM(IO_WRITE, write);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
// Get sort order
SortOrder sortOrder = sortOrderAsc;

View File

@ -224,6 +224,8 @@ restoreBackupSet(void)
{
FUNCTION_LOG_VOID(logLevelDebug);
FUNCTION_AUDIT_STRUCT();
RestoreBackupData result = {0};
MEM_CONTEXT_TEMP_BEGIN()
@ -741,6 +743,8 @@ restoreManifestOwner(const Manifest *const manifest, const String **const rootRe
FUNCTION_LOG_PARAM_P(VOID, rootReplaceGroup);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(manifest != NULL);
MEM_CONTEXT_TEMP_BEGIN()
@ -2009,6 +2013,8 @@ restoreProcessQueue(Manifest *manifest, List **queueList)
FUNCTION_LOG_PARAM_P(LIST, queueList);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(manifest != NULL);
uint64_t result = 0;

View File

@ -201,6 +201,8 @@ verifyInfoFile(const String *pathFileName, bool keepFile, const String *cipherPa
FUNCTION_TEST_PARAM(STRING, cipherPass); // Password to open file if encrypted
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(pathFileName != NULL);
VerifyInfoFile result = {.errorCode = 0};
@ -543,6 +545,8 @@ verifyCreateArchiveIdRange(VerifyArchiveResult *archiveIdResult, StringList *wal
FUNCTION_TEST_PARAM_P(UINT, jobErrorTotal); // Pointer to the overall job error total
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(archiveIdResult != NULL);
ASSERT(walFileList != NULL);
@ -1322,6 +1326,8 @@ verifyRender(const List *const archiveIdResultList, const List *const backupResu
FUNCTION_TEST_PARAM(BOOL, verboseText); // Is verbose output requested?
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(archiveIdResultList != NULL);
ASSERT(backupResultList != NULL);

View File

@ -169,7 +169,7 @@ bz2CompressNew(int level)
OBJ_NEW_BEGIN(Bz2Compress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
Bz2Compress *driver = OBJ_NEW_ALLOC();
Bz2Compress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::Bz2Compress);
*driver = (Bz2Compress)
{

View File

@ -153,7 +153,7 @@ bz2DecompressNew(void)
OBJ_NEW_BEGIN(Bz2Decompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
// Allocate state and set context
Bz2Decompress *driver = OBJ_NEW_ALLOC();
Bz2Decompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::Bz2Decompress);
*driver = (Bz2Decompress)
{

View File

@ -174,7 +174,7 @@ gzCompressNew(int level)
OBJ_NEW_BEGIN(GzCompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
GzCompress *driver = OBJ_NEW_ALLOC();
GzCompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::GzCompress);
*driver = (GzCompress)
{

View File

@ -153,7 +153,7 @@ gzDecompressNew(void)
OBJ_NEW_BEGIN(GzDecompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
// Allocate state and set context
GzDecompress *driver = OBJ_NEW_ALLOC();
GzDecompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::GzDecompress);
*driver = (GzDecompress)
{

View File

@ -255,7 +255,7 @@ lz4CompressNew(int level)
OBJ_NEW_BEGIN(Lz4Compress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
Lz4Compress *driver = OBJ_NEW_ALLOC();
Lz4Compress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::Lz4Compress);
*driver = (Lz4Compress)
{

View File

@ -165,7 +165,7 @@ lz4DecompressNew(void)
OBJ_NEW_BEGIN(Lz4Decompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
Lz4Decompress *driver = OBJ_NEW_ALLOC();
Lz4Decompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::Lz4Decompress);
*driver = (Lz4Decompress){0};
// Create lz4 context

View File

@ -176,7 +176,7 @@ zstCompressNew(int level)
OBJ_NEW_BEGIN(ZstCompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
ZstCompress *driver = OBJ_NEW_ALLOC();
ZstCompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::ZstCompress);
*driver = (ZstCompress)
{

View File

@ -164,7 +164,7 @@ zstDecompressNew(void)
OBJ_NEW_BEGIN(ZstDecompress, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
ZstDecompress *driver = OBJ_NEW_ALLOC();
ZstDecompress *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::ZstDecompress);
*driver = (ZstDecompress)
{

View File

@ -429,7 +429,7 @@ cipherBlockNew(CipherMode mode, CipherType cipherType, const Buffer *pass, Ciphe
OBJ_NEW_BEGIN(CipherBlock, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
CipherBlock *driver = OBJ_NEW_ALLOC();
CipherBlock *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::CipherBlock);
*driver = (CipherBlock)
{

View File

@ -184,7 +184,7 @@ cryptoHashNew(const HashType type)
OBJ_NEW_BEGIN(CryptoHash, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
CryptoHash *driver = OBJ_NEW_ALLOC();
CryptoHash *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::CryptoHash);
*driver = (CryptoHash){0};
// Use local MD5 implementation since FIPS-enabled systems do not allow MD5. This is a bit misguided since there are valid

View File

@ -10,6 +10,54 @@ Debug Routines
#include "common/type/stringStatic.h"
#include "common/type/stringZ.h"
/***********************************************************************************************************************************
These functions allow auditing of child mem contexts and allocations that are left in the calling context when a function exits.
This helps find leaks, i.e. child mem contexts or allocations created in the calling context (and not freed) accidentally.
The FUNCTION_AUDIT_*() macros can be used to annotate functions that do that follow the default behavior, i.e. that a single value
is returned and that is the only value created in the calling context.
***********************************************************************************************************************************/
#if defined(DEBUG_MEM) && defined(DEBUG_TEST_TRACE)
#include "common/macro.h"
#include "common/memContext.h"
// Begin the audit
#define FUNCTION_TEST_MEM_CONTEXT_AUDIT_BEGIN() \
MemContextAuditState MEM_CONTEXT_AUDIT_param = {.memContext = memContextCurrent()}; \
memContextAuditBegin(&MEM_CONTEXT_AUDIT_param)
// End the audit
#define FUNCTION_TEST_MEM_CONTEXT_AUDIT_END(returnType) \
memContextAuditEnd(&MEM_CONTEXT_AUDIT_param, returnType)
// Allow any new mem contexts or allocations in the calling context. These should be fixed and this macro eventually removed.
#define FUNCTION_AUDIT_IF(condition) \
do \
{ \
if (!(condition)) \
MEM_CONTEXT_AUDIT_param.returnTypeAny = true; \
} \
while (0)
// Callbacks are difficult to audit so ignore them. Eventually they should all be removed.
#define FUNCTION_AUDIT_CALLBACK() MEM_CONTEXT_AUDIT_param.returnTypeAny = true
// Helper function that creates new mem contexts or allocations in the calling context. These functions should be static (except
// for interface helpers) but it is not clear that anything else needs to be done.
#define FUNCTION_AUDIT_HELPER() MEM_CONTEXT_AUDIT_param.returnTypeAny = true
// Function returns a struct that has new mem contexts or allocations in the calling context. Find a way to fix these.
#define FUNCTION_AUDIT_STRUCT() MEM_CONTEXT_AUDIT_param.returnTypeAny = true
#else
#define FUNCTION_TEST_MEM_CONTEXT_AUDIT_BEGIN()
#define FUNCTION_TEST_MEM_CONTEXT_AUDIT_END(returnType)
#define FUNCTION_AUDIT_IF(condition)
#define FUNCTION_AUDIT_CALLBACK()
#define FUNCTION_AUDIT_HELPER()
#define FUNCTION_AUDIT_STRUCT()
#endif // DEBUG_TEST_TRACE_MACRO
/***********************************************************************************************************************************
Base function debugging macros
@ -22,6 +70,7 @@ level is set to debug or trace.
#ifdef DEBUG_TEST_TRACE
#define FUNCTION_LOG_BEGIN_BASE(logLevel) \
LogLevel FUNCTION_LOG_LEVEL() = STACK_TRACE_PUSH(logLevel); \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_BEGIN(); \
\
{ \
stackTraceParamLog(); \
@ -34,6 +83,7 @@ level is set to debug or trace.
#else
#define FUNCTION_LOG_BEGIN_BASE(logLevel) \
LogLevel FUNCTION_LOG_LEVEL() = STACK_TRACE_PUSH(logLevel); \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_BEGIN(); \
\
if (logAny(FUNCTION_LOG_LEVEL())) \
{ \
@ -228,6 +278,7 @@ Macros to return function results (or void)
{ \
typePre FUNCTION_LOG_##typeMacroPrefix##_TYPE typePost FUNCTION_LOG_RETURN_result = __VA_ARGS__; \
\
FUNCTION_TEST_MEM_CONTEXT_AUDIT_END(STRINGIFY(FUNCTION_LOG_##typeMacroPrefix##_TYPE)); \
STACK_TRACE_POP(false); \
\
IF_LOG_ANY(FUNCTION_LOG_LEVEL()) \
@ -263,6 +314,7 @@ Macros to return function results (or void)
#define FUNCTION_LOG_RETURN_STRUCT(...) \
do \
{ \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_END("struct"); \
STACK_TRACE_POP(false); \
\
IF_LOG_ANY(FUNCTION_LOG_LEVEL()) \
@ -300,6 +352,8 @@ Ignore DEBUG_TEST_TRACE_MACRO if DEBUG is not defined because the underlying fun
#ifdef DEBUG_TEST_TRACE_MACRO
#define FUNCTION_TEST_BEGIN() \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_BEGIN(); \
\
/* Ensure that FUNCTION_LOG_BEGIN() and FUNCTION_TEST_BEGIN() are not both used in a single function by declaring the */ \
/* same variable that FUNCTION_LOG_BEGIN() uses to track logging */ \
LogLevel FUNCTION_LOG_LEVEL(); \
@ -344,6 +398,7 @@ Ignore DEBUG_TEST_TRACE_MACRO if DEBUG is not defined because the underlying fun
typePre type typePost FUNCTION_TEST_result = __VA_ARGS__; \
\
STACK_TRACE_POP(true); \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_END(STRINGIFY(type)); \
\
return FUNCTION_TEST_result; \
} \
@ -385,6 +440,7 @@ Ignore DEBUG_TEST_TRACE_MACRO if DEBUG is not defined because the underlying fun
(void)FUNCTION_TEST_BEGIN_exists; \
\
STACK_TRACE_POP(true); \
FUNCTION_TEST_MEM_CONTEXT_AUDIT_END("void"); \
return; \
} \
while (0)

View File

@ -96,7 +96,7 @@ ioBufferReadNew(const Buffer *buffer)
OBJ_NEW_BEGIN(IoBufferRead, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoBufferRead *driver = OBJ_NEW_ALLOC();
IoBufferRead *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoRead::IoBufferRead);
*driver = (IoBufferRead)
{

View File

@ -60,7 +60,7 @@ ioBufferWriteNew(Buffer *buffer)
OBJ_NEW_BEGIN(IoBufferWrite, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoBufferWrite *driver = OBJ_NEW_ALLOC();
IoBufferWrite *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoWrite::IoBufferWrite);
*driver = (IoBufferWrite)
{

View File

@ -158,7 +158,7 @@ ioFdReadNew(const String *name, int fd, TimeMSec timeout)
OBJ_NEW_BEGIN(IoFdRead, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoFdRead *driver = OBJ_NEW_ALLOC();
IoFdRead *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoRead::IoFdRead);
*driver = (IoFdRead)
{

View File

@ -114,7 +114,7 @@ ioFdWriteNew(const String *name, int fd, TimeMSec timeout)
OBJ_NEW_BEGIN(IoFdWrite, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoFdWrite *driver = OBJ_NEW_ALLOC();
IoFdWrite *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoWrite::IoFdWrite);
*driver = (IoFdWrite)
{

View File

@ -112,7 +112,7 @@ ioBufferNew(void)
OBJ_NEW_BEGIN(IoBuffer, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoBuffer *driver = OBJ_NEW_ALLOC();
IoBuffer *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::IoBuffer);
*driver = (IoBuffer){0};
this = ioFilterNewP(BUFFER_FILTER_TYPE, driver, NULL, .inOut = ioBufferProcess, .inputSame = ioBufferInputSame);

View File

@ -56,7 +56,7 @@ ioSinkNew(void)
OBJ_NEW_BEGIN(IoSink, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoSink *driver = OBJ_NEW_ALLOC();
IoSink *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::IoSink);
this = ioFilterNewP(SINK_FILTER_TYPE, driver, NULL, .inOut = ioSinkProcess);
}
OBJ_NEW_END();

View File

@ -95,7 +95,7 @@ ioSizeNew(void)
OBJ_NEW_BEGIN(IoSize, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
IoSize *driver = OBJ_NEW_ALLOC();
IoSize *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoFilter::IoSize);
*driver = (IoSize){0};
this = ioFilterNewP(SIZE_FILTER_TYPE, driver, NULL, .in = ioSizeProcess, .result = ioSizeResult);

View File

@ -174,7 +174,7 @@ sckClientNew(const String *const host, const unsigned int port, const TimeMSec t
OBJ_NEW_BEGIN(SocketClient, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
SocketClient *driver = OBJ_NEW_ALLOC();
SocketClient *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoClient::SocketClient);
*driver = (SocketClient)
{

View File

@ -157,7 +157,7 @@ sckServerNew(const String *const address, const unsigned int port, const TimeMSe
OBJ_NEW_BEGIN(SocketServer, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
SocketServer *const driver = OBJ_NEW_ALLOC();
SocketServer *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoServer::SocketServer);
*driver = (SocketServer)
{

View File

@ -181,7 +181,7 @@ sckSessionNew(IoSessionRole role, int fd, const String *host, unsigned int port,
OBJ_NEW_BEGIN(SocketSession, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
SocketSession *driver = OBJ_NEW_ALLOC();
SocketSession *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoSession::SocketSession);
String *name = strNewFmt("%s:%u", strZ(host), port);

View File

@ -370,7 +370,7 @@ tlsClientNew(
OBJ_NEW_BEGIN(TlsClient, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
TlsClient *driver = OBJ_NEW_ALLOC();
TlsClient *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoClient::TlsClient);
*driver = (TlsClient)
{

View File

@ -296,7 +296,7 @@ tlsServerNew(
OBJ_NEW_BEGIN(TlsServer, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
TlsServer *const driver = OBJ_NEW_ALLOC();
TlsServer *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoServer::TlsServer);
*driver = (TlsServer)
{

View File

@ -365,7 +365,7 @@ tlsSessionNew(SSL *session, IoSession *ioSession, TimeMSec timeout)
OBJ_NEW_BEGIN(TlsSession, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
TlsSession *driver = OBJ_NEW_ALLOC();
TlsSession *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), IoSession::TlsSession);
*driver = (TlsSession)
{

View File

@ -93,6 +93,8 @@ lockReadFileData(const String *const lockFile, const int fd)
FUNCTION_LOG_PARAM(INT, fd);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL);
ASSERT(fd != -1);
@ -143,6 +145,8 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
FUNCTION_LOG_PARAM(BOOL, param.remove);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL);
LockReadResult result = {.status = lockReadStatusValid};
@ -204,6 +208,8 @@ lockRead(const String *const lockPath, const String *const stanza, const LockTyp
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
LockReadResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()

View File

@ -52,6 +52,7 @@ struct MemContext
{
#ifdef DEBUG
const char *name; // Indicates what the context is being used for
uint64_t sequenceNew; // Sequence when this context was created (used for audit)
bool active:1; // Is the context currently active?
#endif
MemQty childQty:2; // How many child contexts can this context have?
@ -228,6 +229,165 @@ static struct MemContextStack
static unsigned int memContextCurrentStackIdx = 0;
static unsigned int memContextMaxStackIdx = 0;
/***********************************************************************************************************************************
***********************************************************************************************************************************/
#ifdef DEBUG
static uint64_t memContextSequence = 0;
FN_EXTERN void
memContextAuditBegin(MemContextAuditState *const state)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, state);
FUNCTION_TEST_END();
ASSERT(state != NULL);
ASSERT(state->memContext != NULL);
ASSERT(state->memContext == memContextTop() || state->memContext->sequenceNew != 0);
if (state->memContext->childInitialized)
{
ASSERT(state->memContext->childQty != memQtyNone);
if (state->memContext->childQty == memQtyOne)
{
MemContextChildOne *const memContextChild = memContextChildOne(state->memContext);
if (memContextChild->context != NULL)
state->sequenceContextNew = memContextChild->context->sequenceNew;
}
else
{
ASSERT(state->memContext->childQty == memQtyMany);
MemContextChildMany *const memContextChild = memContextChildMany(state->memContext);
for (unsigned int contextIdx = 0; contextIdx < memContextChild->listSize; contextIdx++)
{
if (memContextChild->list[contextIdx] != NULL &&
memContextChild->list[contextIdx]->sequenceNew > state->sequenceContextNew)
{
state->sequenceContextNew = memContextChild->list[contextIdx]->sequenceNew;
}
}
}
}
FUNCTION_TEST_RETURN_VOID();
}
static bool
memContextAuditNameMatch(const char *const actual, const char *const expected)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRINGZ, actual);
FUNCTION_TEST_PARAM(STRINGZ, expected);
FUNCTION_TEST_END();
ASSERT(actual != NULL);
ASSERT(expected != NULL);
unsigned int actualIdx = 0;
while (actual[actualIdx] != '\0' && actual[actualIdx] == expected[actualIdx])
actualIdx++;
FUNCTION_TEST_RETURN(
BOOL,
(actual[actualIdx] == '\0' || strncmp(actual + actualIdx, "::", 2) == 0) &&
(expected[actualIdx] == '\0' || strcmp(expected + actualIdx, " *") == 0));
}
FN_EXTERN void
memContextAuditEnd(const MemContextAuditState *const state, const char *const returnType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, state);
FUNCTION_TEST_PARAM(STRINGZ, returnType);
FUNCTION_TEST_END();
if (state->returnTypeAny)
FUNCTION_TEST_RETURN_VOID();
if (state->memContext->childInitialized)
{
ASSERT(state->memContext->childQty != memQtyNone);
const char *returnTypeInvalid = NULL;
const char *returnTypeFound = NULL;
if (state->memContext->childQty == memQtyOne)
{
MemContextChildOne *const memContextChild = memContextChildOne(state->memContext);
if (memContextChild->context != NULL && memContextChild->context->sequenceNew > state->sequenceContextNew &&
!memContextAuditNameMatch(memContextChild->context->name, returnType))
{
returnTypeInvalid = memContextChild->context->name;
}
}
else
{
ASSERT(state->memContext->childQty == memQtyMany);
MemContextChildMany *const memContextChild = memContextChildMany(state->memContext);
for (unsigned int contextIdx = 0; contextIdx < memContextChild->listSize; contextIdx++)
{
if (memContextChild->list[contextIdx] != NULL &&
memContextChild->list[contextIdx]->sequenceNew > state->sequenceContextNew)
{
if (memContextAuditNameMatch(memContextChild->list[contextIdx]->name, returnType))
{
if (returnTypeFound != NULL)
{
returnTypeInvalid = memContextChild->list[contextIdx]->name;
break;
}
returnTypeFound = memContextChild->list[contextIdx]->name;
}
else
{
returnTypeInvalid = memContextChild->list[contextIdx]->name;
break;
}
}
}
}
if (returnTypeInvalid != NULL)
{
if (returnTypeFound != NULL)
{
THROW_FMT(
AssertError, "expected return type '%s' already found but also found '%s'", returnTypeFound, returnTypeInvalid);
}
else
{
THROW_FMT(
AssertError, "expected return type '%s' but found '%s'", returnType, returnTypeInvalid);
}
}
}
FUNCTION_TEST_RETURN_VOID();
}
FN_EXTERN void *
memContextAuditAllocExtraName(void *const allocExtra, const char *const name)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, allocExtra);
FUNCTION_TEST_PARAM(STRINGZ, name);
FUNCTION_TEST_END();
memContextFromAllocExtra(allocExtra)->name = name;
FUNCTION_TEST_RETURN_P(VOID, allocExtra);
}
#endif
/***********************************************************************************************************************************
Wrapper around malloc() with error handling
***********************************************************************************************************************************/
@ -432,6 +592,9 @@ memContextNew(
// Set the context name
.name = name,
// Set audit sequence
.sequenceNew = ++memContextSequence,
// Set new context active
.active = true,
#endif

View File

@ -38,6 +38,33 @@ Space is reserved for this many allocations when a context is created. When mor
***********************************************************************************************************************************/
#define MEM_CONTEXT_ALLOC_INITIAL_SIZE 4
/***********************************************************************************************************************************
Functions and macros to audit a mem context by detecting new child contexts/allocations that were created begin the begin/end but
are not the expected return type.
***********************************************************************************************************************************/
#if defined(DEBUG)
typedef struct MemContextAuditState
{
MemContext *memContext; // Mem context to audit
bool returnTypeAny; // Skip auditing for this mem context
uint64_t sequenceContextNew; // Max sequence for new contexts at beginning
} MemContextAuditState;
// Begin the audit
FN_EXTERN void memContextAuditBegin(MemContextAuditState *state);
// End the audit and make sure the return type is as expected
FN_EXTERN void memContextAuditEnd(const MemContextAuditState *state, const char *returnTypeDefault);
// Rename a mem context using the extra allocation pointer
#define MEM_CONTEXT_AUDIT_ALLOC_EXTRA_NAME(this, name) memContextAuditAllocExtraName(this, #name)
FN_EXTERN void *memContextAuditAllocExtraName(void *allocExtra, const char *name);
#else
#define MEM_CONTEXT_AUDIT_ALLOC_EXTRA_NAME(this, name) this
#endif
/***********************************************************************************************************************************
Memory management functions

View File

@ -131,6 +131,8 @@ kvPutInternal(KeyValue *this, const Variant *key, Variant *value)
ASSERT(this != NULL);
ASSERT(key != NULL);
FUNCTION_AUDIT_HELPER();
// Find the key
unsigned int listIdx = kvGetIdx(this, key);

View File

@ -41,6 +41,15 @@ OBJ_NEW_END();
#define OBJ_NEW_END() \
MEM_CONTEXT_NEW_END()
/***********************************************************************************************************************************
Rename an object for auditing purposes. The original name for an object is based on the type used to create the object but a
different name might be needed to identify it during auditing. For example, a filter named IoSink would need to be renamed to
IoFilter to identify it as a filter for auditing.
This only has an effect in test builds.
***********************************************************************************************************************************/
#define OBJ_NAME(this, name) MEM_CONTEXT_AUDIT_ALLOC_EXTRA_NAME(this, name)
/***********************************************************************************************************************************
Used in interface function parameter lists to discourage use of the untyped thisVoid parameter, e.g.:

View File

@ -1066,7 +1066,7 @@ pckReadPack(PackRead *const this, PckReadPackParam param)
pckReadTag(this, &param.id, pckTypeMapPack, false);
// Get the pack size
Buffer *result = bufNew(this->tagNextSize);
Buffer *const result = OBJ_NAME(bufNew(this->tagNextSize), Pack::Buffer);
// Read the pack out in chunks
while (bufUsed(result) < bufSize(result))
@ -1939,7 +1939,7 @@ pckWriteResult(PackWrite *const this)
{
ASSERT(this->tagStack.top == NULL);
result = (Pack *)this->buffer;
result = (Pack *)OBJ_NAME(this->buffer, Pack::Buffer);
}
FUNCTION_TEST_RETURN(PACK, result);

View File

@ -137,7 +137,7 @@ Pack Functions
FN_INLINE_ALWAYS Pack *
pckDup(const Pack *const this)
{
return (Pack *)bufDup((const Buffer *)this);
return (Pack *)OBJ_NAME(bufDup((const Buffer *)this), Pack::Buffer);
}
// Cast Buffer to Pack

View File

@ -21,7 +21,7 @@ Constructors
FN_INLINE_ALWAYS StringList *
strLstNew(void)
{
return (StringList *)lstNewP(sizeof(String *), .comparator = lstComparatorStr);
return (StringList *)OBJ_NAME(lstNewP(sizeof(String *), .comparator = lstComparatorStr), StringList::List);
}
// Split a string into a string list based on a delimiter

View File

@ -211,7 +211,7 @@ varNewBool(bool data)
OBJ_NEW_BEGIN(VariantBool)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantBool);
*this = (VariantBool)
{
@ -320,7 +320,7 @@ varNewInt(int data)
OBJ_NEW_BEGIN(VariantInt)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantInt);
*this = (VariantInt)
{
@ -436,7 +436,7 @@ varNewInt64(int64_t data)
OBJ_NEW_BEGIN(VariantInt64)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantInt64);
*this = (VariantInt64)
{
@ -536,7 +536,7 @@ varNewUInt(unsigned int data)
OBJ_NEW_BEGIN(VariantUInt)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantUInt);
*this = (VariantUInt)
{
@ -661,7 +661,7 @@ varNewUInt64(uint64_t data)
OBJ_NEW_BEGIN(VariantUInt64)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantUInt64);
*this = (VariantUInt64)
{
@ -773,7 +773,7 @@ varNewKv(KeyValue *data)
OBJ_NEW_BEGIN(VariantKeyValue, .childQty = 1)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantKeyValue);
*this = (VariantKeyValue)
{
@ -826,7 +826,7 @@ varNewStr(const String *data)
OBJ_NEW_BEGIN(VariantString, .childQty = 1)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantString);
*this = (VariantString)
{
@ -844,7 +844,7 @@ varNewStr(const String *data)
OBJ_NEW_EXTRA_BEGIN(VariantString, (uint16_t)(allocExtra))
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantString);
*this = (VariantString)
{
@ -982,7 +982,7 @@ varNewVarLst(const VariantList *data)
OBJ_NEW_BEGIN(VariantVariantList, .childQty = 1)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), Variant::VariantVariantList);
*this = (VariantVariantList)
{

View File

@ -19,7 +19,7 @@ Constructors
FN_INLINE_ALWAYS VariantList *
varLstNew(void)
{
return (VariantList *)lstNewP(sizeof(Variant *));
return (VariantList *)OBJ_NAME(lstNewP(sizeof(Variant *)), VariantList::List);
}
// Create VariantList from StringList

View File

@ -79,7 +79,7 @@ static XmlNodeList *
xmlNodeLstNew(void)
{
FUNCTION_TEST_VOID();
FUNCTION_TEST_RETURN(XML_NODE_LIST, (XmlNodeList *)lstNewP(sizeof(XmlNode *)));
FUNCTION_TEST_RETURN(XML_NODE_LIST, (XmlNodeList *)OBJ_NAME(lstNewP(sizeof(XmlNode *)), XmlNodeList::List));
}
/**********************************************************************************************************************************/

View File

@ -67,6 +67,8 @@ cfgLoadUpdateOption(void)
{
FUNCTION_LOG_VOID(logLevelTrace);
FUNCTION_AUDIT_HELPER();
// Make sure the repo option is set for the stanza-delete command when more than one repo is configured or the first configured
// repo is not key 1.
if (!cfgCommandHelp() && cfgOptionValid(cfgOptRepo) && cfgCommand() == cfgCmdStanzaDelete &&

View File

@ -765,6 +765,8 @@ cfgParseOptionalRule(
FUNCTION_TEST_PARAM(ENUM, optionId);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(optionalRuleType != 0);
ASSERT(commandId < CFG_COMMAND_TOTAL);
ASSERT(optionId < CFG_OPTION_TOTAL);
@ -1103,7 +1105,7 @@ cfgParseOptionKeyIdxName(ConfigOption optionId, unsigned int keyIdx)
FUNCTION_TEST_RETURN_CONST(
STRINGZ,
zNewFmt(
"%s%u%s", parseRuleOptionGroup[parseRuleOption[optionId].groupId].name, keyIdx + 1,
"%s%u%s", parseRuleOptionGroup[parseRuleOption[optionId].groupId].name, keyIdx + 1,
parseRuleOption[optionId].name + strlen(parseRuleOptionGroup[parseRuleOption[optionId].groupId].name)));
}
@ -1222,6 +1224,8 @@ cfgFileLoadPart(String **config, const Buffer *configPart)
FUNCTION_LOG_PARAM(BUFFER, configPart);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
if (configPart != NULL)
{
String *configPartStr = strNewBuf(configPart);
@ -1265,6 +1269,8 @@ cfgFileLoad( // NOTE: Pas
FUNCTION_LOG_PARAM(STRING, origConfigDefault);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(optionList != NULL);
ASSERT(optConfigDefault != NULL);
ASSERT(optConfigIncludePathDefault != NULL);

View File

@ -377,6 +377,8 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
FUNCTION_LOG_PARAM(BOOL, archiveCheck);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
DbBackupStartResult result = {.lsn = NULL};
@ -541,6 +543,8 @@ dbBackupStop(Db *this)
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
DbBackupStopResult result = {.lsn = NULL};

View File

@ -56,6 +56,8 @@ dbGet(bool primaryOnly, bool primaryRequired, bool standbyRequired)
FUNCTION_LOG_PARAM(BOOL, standbyRequired);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(!(primaryOnly && standbyRequired));
DbGetResult result = {0};

View File

@ -163,6 +163,8 @@ infoLoadCallback(void *const data, const String *const section, const String *co
FUNCTION_TEST_PARAM(STRING, value);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(data != NULL);
ASSERT(section != NULL);
ASSERT(key != NULL);
@ -250,6 +252,8 @@ infoNewLoad(IoRead *read, InfoLoadNewCallback *callbackFunction, void *callbackD
FUNCTION_LOG_PARAM_P(VOID, callbackData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(read != NULL);
ASSERT(callbackFunction != NULL);
ASSERT(callbackData != NULL);
@ -336,6 +340,8 @@ infoSaveValue(InfoSave *const infoSaveData, const char *const section, const cha
FUNCTION_TEST_PARAM(STRING, jsonValue);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(infoSaveData != NULL);
ASSERT(section != NULL);
ASSERT(key != NULL);
@ -450,6 +456,8 @@ infoCipherPassSet(Info *this, const String *cipherPass)
FUNCTION_TEST_PARAM(STRING, cipherPass);
FUNCTION_TEST_END();
FUNCTION_AUDIT_IF(memContextCurrent() != this->memContext); // Do not audit calls from within the object
ASSERT(this != NULL);
MEM_CONTEXT_BEGIN(this->memContext)

View File

@ -47,6 +47,8 @@ infoBackupNewInternal(void)
{
FUNCTION_TEST_VOID();
FUNCTION_AUDIT_HELPER();
InfoBackup *this = OBJ_NEW_ALLOC();
*this = (InfoBackup)
@ -252,6 +254,8 @@ infoBackupSaveCallback(void *const data, const String *const sectionNext, InfoSa
FUNCTION_TEST_PARAM(INFO_SAVE, infoSaveData);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(data != NULL);
ASSERT(infoSaveData != NULL);

View File

@ -43,6 +43,8 @@ infoPgNewInternal(InfoPgType type)
FUNCTION_TEST_PARAM(STRING_ID, type);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
InfoPg *this = OBJ_NEW_ALLOC();
*this = (InfoPg)
@ -106,6 +108,8 @@ infoPgLoadCallback(void *const data, const String *const section, const String *
FUNCTION_TEST_PARAM(STRING, value);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(data != NULL);
ASSERT(section != NULL);
ASSERT(key != NULL);
@ -285,6 +289,8 @@ infoPgSaveCallback(void *const data, const String *const sectionNext, InfoSave *
FUNCTION_TEST_PARAM(INFO_SAVE, infoSaveData);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(data != NULL);
ASSERT(infoSaveData != NULL);

View File

@ -499,6 +499,8 @@ manifestNewInternal(void)
{
FUNCTION_TEST_VOID();
FUNCTION_AUDIT_HELPER();
Manifest *this = OBJ_NEW_ALLOC();
*this = (Manifest)
@ -546,6 +548,7 @@ static ManifestLinkCheck
manifestLinkCheckInit(void)
{
FUNCTION_TEST_VOID();
FUNCTION_AUDIT_STRUCT();
FUNCTION_TEST_RETURN_TYPE(
ManifestLinkCheck, (ManifestLinkCheck){.linkList = lstNewP(sizeof(ManifestLinkCheckItem), .comparator = lstComparatorStr)});
}
@ -754,6 +757,8 @@ manifestBuildInfo(
FUNCTION_TEST_PARAM(STORAGE_INFO, *info);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(buildData != NULL);
ASSERT(manifestParentName != NULL);
ASSERT(pgPath != NULL);
@ -1817,6 +1822,8 @@ manifestLoadCallback(void *callbackData, const String *const section, const Stri
FUNCTION_TEST_PARAM(STRING, value);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(callbackData != NULL);
ASSERT(section != NULL);
ASSERT(key != NULL);
@ -2307,6 +2314,8 @@ manifestSaveCallback(void *const callbackData, const String *const sectionNext,
FUNCTION_TEST_PARAM(INFO_SAVE, infoSaveData);
FUNCTION_TEST_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(callbackData != NULL);
ASSERT(infoSaveData != NULL);

View File

@ -195,6 +195,8 @@ protocolLocalExec(
FUNCTION_TEST_PARAM(UINT, processId);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(helper != NULL);
MEM_CONTEXT_TEMP_BEGIN()
@ -671,6 +673,8 @@ protocolRemoteExec(
FUNCTION_TEST_PARAM(UINT, processId);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(helper != NULL);
MEM_CONTEXT_TEMP_BEGIN()

View File

@ -113,6 +113,8 @@ protocolServerCommandGet(ProtocolServer *const this)
FUNCTION_LOG_PARAM(PROTOCOL_SERVER, this);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ProtocolServerCommandGetResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()

View File

@ -127,7 +127,7 @@ storageReadAzureNew(
OBJ_NEW_BEGIN(StorageReadAzure, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageReadAzure *driver = OBJ_NEW_ALLOC();
StorageReadAzure *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageRead::StorageReadAzure);
*driver = (StorageReadAzure)
{

View File

@ -315,6 +315,8 @@ storageAzureListInternal(
FUNCTION_LOG_PARAM_P(VOID, callbackData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(this != NULL);
ASSERT(path != NULL);
@ -734,7 +736,7 @@ storageAzureNew(
OBJ_NEW_BEGIN(StorageAzure, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageAzure *driver = OBJ_NEW_ALLOC();
StorageAzure *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), Storage::StorageAzure);
*driver = (StorageAzure)
{

View File

@ -276,7 +276,7 @@ storageWriteAzureNew(StorageAzure *storage, const String *name, uint64_t fileId,
OBJ_NEW_BEGIN(StorageWriteAzure, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageWriteAzure *driver = OBJ_NEW_ALLOC();
StorageWriteAzure *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageWrite::StorageWriteAzure);
*driver = (StorageWriteAzure)
{

View File

@ -134,7 +134,7 @@ storageReadGcsNew(
OBJ_NEW_BEGIN(StorageReadGcs, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageReadGcs *driver = OBJ_NEW_ALLOC();
StorageReadGcs *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageRead::StorageReadGcs);
*driver = (StorageReadGcs)
{

View File

@ -118,6 +118,8 @@ storageGcsAuthToken(HttpRequest *const request, const time_t timeBegin)
FUNCTION_TEST_PARAM(TIME, timeBegin);
FUNCTION_TEST_END();
FUNCTION_AUDIT_STRUCT();
StorageGcsAuthTokenResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
@ -246,6 +248,8 @@ storageGcsAuthService(StorageGcs *this, time_t timeBegin)
FUNCTION_TEST_PARAM(TIME, timeBegin);
FUNCTION_TEST_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(timeBegin > 0);
@ -289,6 +293,8 @@ storageGcsAuthAuto(StorageGcs *this, time_t timeBegin)
FUNCTION_TEST_PARAM(TIME, timeBegin);
FUNCTION_TEST_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(timeBegin > 0);
@ -543,6 +549,8 @@ storageGcsListInternal(
FUNCTION_LOG_PARAM_P(VOID, callbackData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(this != NULL);
ASSERT(path != NULL);
@ -965,7 +973,7 @@ storageGcsNew(
OBJ_NEW_BEGIN(StorageGcs, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageGcs *driver = OBJ_NEW_ALLOC();
StorageGcs *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), Storage::StorageGcs);
*driver = (StorageGcs)
{

View File

@ -335,7 +335,7 @@ storageWriteGcsNew(StorageGcs *storage, const String *name, size_t chunkSize)
OBJ_NEW_BEGIN(StorageWriteGcs, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageWriteGcs *driver = OBJ_NEW_ALLOC();
StorageWriteGcs *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageWrite::StorageWriteGcs);
*driver = (StorageWriteGcs)
{

View File

@ -224,7 +224,7 @@ storageReadPosixNew(
OBJ_NEW_BEGIN(StorageReadPosix, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
StorageReadPosix *driver = OBJ_NEW_ALLOC();
StorageReadPosix *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageRead::StorageReadPosix);
*driver = (StorageReadPosix)
{

View File

@ -48,6 +48,8 @@ storagePosixInfo(THIS_VOID, const String *file, StorageInfoLevel level, StorageI
FUNCTION_LOG_PARAM(BOOL, param.followLink);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(file != NULL);
@ -167,6 +169,8 @@ storagePosixListEntry(
FUNCTION_TEST_PARAM(ENUM, level);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(this != NULL);
ASSERT(list != NULL);
ASSERT(path != NULL);
@ -612,7 +616,7 @@ storagePosixNewInternal(
OBJ_NEW_BEGIN(StoragePosix, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StoragePosix *driver = OBJ_NEW_ALLOC();
StoragePosix *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), Storage::StoragePosix);
*driver = (StoragePosix)
{

View File

@ -249,7 +249,7 @@ storageWritePosixNew(
OBJ_NEW_BEGIN(StorageWritePosix, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
StorageWritePosix *driver = OBJ_NEW_ALLOC();
StorageWritePosix *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageWrite::StorageWritePosix);
*driver = (StorageWritePosix)
{

View File

@ -34,6 +34,8 @@ storageReadNew(void *driver, const StorageReadInterface *interface)
FUNCTION_LOG_PARAM_P(STORAGE_READ_INTERFACE, interface);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(driver != NULL);
ASSERT(interface != NULL);

View File

@ -132,6 +132,8 @@ storageRemoteFeatureProtocol(PackRead *const param, ProtocolServer *const server
FUNCTION_LOG_PARAM(PROTOCOL_SERVER, server);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(param == NULL);
ASSERT(server != NULL);
@ -194,6 +196,8 @@ storageRemoteInfoProtocolPut(
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(data != NULL);
ASSERT(write != NULL);
ASSERT(info != NULL);

View File

@ -306,7 +306,7 @@ storageReadRemoteNew(
OBJ_NEW_BEGIN(StorageReadRemote, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), StorageRead::StorageReadRemote);
*this = (StorageReadRemote)
{

View File

@ -44,6 +44,8 @@ storageRemoteInfoGet(StorageRemoteInfoData *const data, PackRead *const read, St
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(data != NULL);
ASSERT(read != NULL);
ASSERT(info != NULL);
@ -126,6 +128,8 @@ storageRemoteInfo(THIS_VOID, const String *file, StorageInfoLevel level, Storage
FUNCTION_LOG_PARAM(BOOL, param.followLink);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
StorageInfo result = {.level = level};
@ -486,7 +490,7 @@ storageRemoteNew(
OBJ_NEW_BEGIN(StorageRemote, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageRemote *driver = OBJ_NEW_ALLOC();
StorageRemote *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), Storage::StorageRemote);
*driver = (StorageRemote)
{

View File

@ -213,7 +213,7 @@ storageWriteRemoteNew(
OBJ_NEW_BEGIN(StorageWriteRemote, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
this = OBJ_NEW_ALLOC();
this = OBJ_NAME(OBJ_NEW_ALLOC(), StorageWrite::StorageWriteRemote);
*this = (StorageWriteRemote)
{

View File

@ -130,7 +130,7 @@ storageReadS3New(
OBJ_NEW_BEGIN(StorageReadS3, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageReadS3 *driver = OBJ_NEW_ALLOC();
StorageReadS3 *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageRead::StorageReadS3);
*driver = (StorageReadS3)
{

View File

@ -622,6 +622,8 @@ storageS3ListInternal(
FUNCTION_LOG_PARAM_P(VOID, callbackData);
FUNCTION_LOG_END();
FUNCTION_AUDIT_CALLBACK();
ASSERT(this != NULL);
ASSERT(path != NULL);
@ -1131,7 +1133,7 @@ storageS3New(
OBJ_NEW_BEGIN(StorageS3, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageS3 *driver = OBJ_NEW_ALLOC();
StorageS3 *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), Storage::StorageS3);
*driver = (StorageS3)
{

View File

@ -280,7 +280,7 @@ storageWriteS3New(StorageS3 *storage, const String *name, size_t partSize)
OBJ_NEW_BEGIN(StorageWriteS3, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = MEM_CONTEXT_QTY_MAX)
{
StorageWriteS3 *driver = OBJ_NEW_ALLOC();
StorageWriteS3 *const driver = OBJ_NAME(OBJ_NEW_ALLOC(), StorageWrite::StorageWriteS3);
*driver = (StorageWriteS3)
{

View File

@ -46,6 +46,8 @@ storageNew(
FUNCTION_LOG_PARAM(STORAGE_INTERFACE, interface);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(type != 0);
ASSERT(strSize(path) >= 1 && strZ(path)[0] == '/');
ASSERT(driver != NULL);
@ -243,6 +245,8 @@ storageInfo(const Storage *this, const String *fileExp, StorageInfoParam param)
FUNCTION_LOG_PARAM(BOOL, param.noPathEnforce);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(this->pub.interface.info != NULL);

View File

@ -37,6 +37,8 @@ storageWriteNew(void *driver, const StorageWriteInterface *interface)
FUNCTION_LOG_PARAM_P(STORAGE_WRITE_INTERFACE, interface);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(driver != NULL);
ASSERT(interface != NULL);

View File

@ -79,13 +79,6 @@ unit:
- common/debug
- common/type/stringStatic
# ----------------------------------------------------------------------------------------------------------------------------
- name: type-convert
total: 12
coverage:
- common/type/convert
# ----------------------------------------------------------------------------------------------------------------------------
- name: assert-off
total: 1
@ -109,7 +102,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: mem-context
total: 7
total: 8
feature: memContext
coverage:
@ -118,6 +111,13 @@ unit:
depend:
- common/type/convert
# ----------------------------------------------------------------------------------------------------------------------------
- name: type-convert
total: 12
coverage:
- common/type/convert
# ----------------------------------------------------------------------------------------------------------------------------
- name: time
total: 3

View File

@ -23,6 +23,8 @@ testDefParseModuleList(Yaml *const yaml, List *const moduleList)
FUNCTION_LOG_PARAM(LIST, moduleList);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
// Global lists to be copied to next test
StringList *const globalDependList = strLstNew();
StringList *const globalFeatureList = strLstNew();
@ -313,6 +315,8 @@ testDefParse(const Storage *const storageRepo)
FUNCTION_LOG_PARAM(STORAGE, storageRepo);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
// Module list
List *const moduleList = lstNewP(sizeof(TestDefModule), .comparator = lstComparatorStr);

View File

@ -47,6 +47,8 @@ cmdTestPathCreate(const Storage *const storage, const String *const path)
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_END();
FUNCTION_AUDIT_HELPER();
ASSERT(storage != NULL);
TRY_BEGIN()

View File

@ -73,7 +73,7 @@ testRun(void)
{
TEST_TITLE("struct size");
TEST_RESULT_UINT(sizeof(MemContext), TEST_64BIT() ? 24 : 16, "MemContext size");
TEST_RESULT_UINT(sizeof(MemContext), TEST_64BIT() ? 32 : 24, "MemContext size");
TEST_RESULT_UINT(sizeof(MemContextChildMany), TEST_64BIT() ? 16 : 12, "MemContextChildMany size");
TEST_RESULT_UINT(sizeof(MemContextAllocMany), TEST_64BIT() ? 16 : 12, "MemContextAllocMany size");
TEST_RESULT_UINT(sizeof(MemContextCallbackOne), TEST_64BIT() ? 16 : 8, "MemContextCallbackOne size");
@ -174,7 +174,7 @@ testRun(void)
MEM_CONTEXT_INITIAL_SIZE, "context child list initial size");
// This test will change if the contexts above change
TEST_RESULT_UINT(memContextSize(memContextTop()), TEST_64BIT() ? 584 : 376, "check size");
TEST_RESULT_UINT(memContextSize(memContextTop()), TEST_64BIT() ? 656 : 448, "check size");
TEST_ERROR(
memContextFree(memContextChildMany(memContextTop())->list[MEM_CONTEXT_INITIAL_SIZE]), AssertError,
@ -196,10 +196,9 @@ testRun(void)
{
TEST_TITLE("struct size");
TEST_RESULT_UINT(sizeof(MemContextAlloc), 8, "MemContextAlloc size");
TEST_RESULT_UINT(sizeof(MemContextAlloc), 8, "check MemContextAlloc size (same for 32/64 bit)");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_UINT(sizeof(MemContextAlloc), 8, "check MemContextAlloc size (same for 32/64 bit)");
TEST_RESULT_PTR(MEM_CONTEXT_ALLOC_BUFFER((void *)1), (void *)(sizeof(MemContextAlloc) + 1), "check buffer macro");
TEST_RESULT_PTR(MEM_CONTEXT_ALLOC_HEADER((void *)sizeof(MemContextAlloc)), (void *)0, "check header macro");
@ -255,7 +254,7 @@ testRun(void)
memContextAllocMany(memContextCurrent())->freeIdx, MEM_CONTEXT_ALLOC_INITIAL_SIZE + 3, "check alloc free idx");
// This test will change if the allocations above change
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 209 : 145, "check size");
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 217 : 153, "check size");
TEST_ERROR(
memFree(NULL), AssertError,
@ -282,7 +281,7 @@ testRun(void)
TEST_ASSIGN(buffer, memNew(200), "new");
// This test will change if the allocations above change
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 240 : 228, "check size");
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 248 : 236, "check size");
TEST_RESULT_VOID(memContextSwitch(memContextTop()), "switch to top");
TEST_RESULT_VOID(memContextFree(memContext), "context free");
@ -298,7 +297,7 @@ testRun(void)
TEST_RESULT_VOID(memFree(buffer), "free");
// This test will change if the allocations above change
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 32 : 20, "check size");
TEST_RESULT_UINT(memContextSize(memContextCurrent()), TEST_64BIT() ? 40 : 28, "check size");
TEST_RESULT_VOID(memContextSwitch(memContextTop()), "switch to top");
TEST_RESULT_VOID(memContextFree(memContext), "context free");
@ -535,6 +534,140 @@ testRun(void)
TEST_RESULT_PTR(memContextChildOne(memContextParent2)->context, memContextChild, "check parent2");
}
// *****************************************************************************************************************************
if (testBegin("memContextAudit*s()"))
{
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("one child context void success with no child context");
MemContext *parent = memContextNewP("to be renamed", .allocExtra = 8, .childQty = 1);
memContextKeep();
memContextSwitch(parent);
TEST_RESULT_VOID(memContextAuditAllocExtraName(memContextAllocExtra(parent), "one context child"), "rename context");
TEST_RESULT_Z(parent->name, "one context child", "check name");
MemContextAuditState audit = {.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("return immediately on any");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
audit.returnTypeAny = true;
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("one child context void success");
MemContext *child = memContextNewP("child", .childQty = 1);
memContextKeep();
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
memContextFree(child);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("one child context void success (freed context)");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("one child context match success");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
child = memContextNewP("child", .childQty = 1);
memContextKeep();
TEST_RESULT_VOID(memContextAuditEnd(&audit, "child"), "audit end");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "child *"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("one child context match failure");
TEST_ERROR(memContextAuditEnd(&audit, "child2"), AssertError, "expected return type 'child2' but found 'child'");
TEST_ERROR(memContextAuditEnd(&audit, "chil"), AssertError, "expected return type 'chil' but found 'child'");
memContextSwitchBack();
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("many child context void success with no child context");
parent = memContextNewP("many context child", .childQty = MEM_CONTEXT_QTY_MAX);
memContextKeep();
memContextSwitch(parent);
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("many child context void success");
child = memContextNewP("child", .childQty = 1);
memContextKeep();
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "void"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("many child context success with child context created before");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
TEST_RESULT_VOID(memContextAuditEnd(&audit, "child"), "audit end");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("many child context failure with multiple matches");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
memContextNewP("child2::extra", .childQty = 1);
memContextKeep();
memContextFree(child);
memContextNewP("child2::extra", .childQty = 1);
memContextKeep();
TEST_ERROR(
memContextAuditEnd(&audit, "child2"), AssertError,
"expected return type 'child2::extra' already found but also found 'child2::extra'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("many child context failure with no match");
audit = (MemContextAuditState){.memContext = memContextCurrent()};
TEST_RESULT_VOID(memContextAuditBegin(&audit), "audit begin");
memContextNewP("child3::extra", .childQty = 1);
memContextKeep();
TEST_ERROR(
memContextAuditEnd(&audit, "child2"), AssertError,
"expected return type 'child2' but found 'child3::extra'");
memContextSwitchBack();
}
memContextFree(memContextTop());
FUNCTION_HARNESS_RETURN_VOID();