From 221803661e56b2d76c1866b4fbd374265c2e7e1d Mon Sep 17 00:00:00 2001 From: Joseph Hickey Date: Wed, 20 May 2020 14:39:11 -0400 Subject: [PATCH] Fix #479, add name association to stub arguments Add the capability to store parameter names in addtion to pointers as part of the stub argument context data. Macroize the UT_Stub_RegisterContext function utstubs.h to also pass the parameter name in addition to the pointer. Also add hook-side accessor functions/macros to make it easier and more reliable to get this information from the context data. --- ut_assert/inc/utstubs.h | 102 ++++++++++++++++++++++++++++++++++++- ut_assert/src/utstubs.c | 109 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 4 deletions(-) diff --git a/ut_assert/inc/utstubs.h b/ut_assert/inc/utstubs.h index 765fd506d..afe0fb170 100644 --- a/ut_assert/inc/utstubs.h +++ b/ut_assert/inc/utstubs.h @@ -53,8 +53,29 @@ typedef cpuaddr UT_EntryKey_t; * Maximum size of a callback hook context list * * This is the maximum number of function arguments that can be passed to a hook + * Note that OS_TaskCreate() has (possibly) the highest parameter count in OSAL with 7 parameters */ -#define UT_STUBCONTEXT_MAXSIZE 4 +#define UT_STUBCONTEXT_MAXSIZE 8 + +/** + * Identifies the type of value stored in the ArgPtr field of a UT_StubContext_t object + */ +typedef enum +{ + UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED = 0, + UT_STUBCONTEXT_ARG_TYPE_DIRECT, /**< Indicates "ArgPtr" is a direct copy of the actual parameter value */ + UT_STUBCONTEXT_ARG_TYPE_INDIRECT /**< Indicates "ArgPtr" is a pointer to the argument value on the stack */ +} UT_StubContext_Arg_Type_t; + +/** + * Complete Metadata associated with a context argument + */ +typedef struct +{ + UT_StubContext_Arg_Type_t Type; + const char *Name; + size_t Size; +} UT_StubArgMetaData_t; /** * Structure to hold context data for callback hooks @@ -63,6 +84,7 @@ typedef struct { uint32 ArgCount; const void *ArgPtr[UT_STUBCONTEXT_MAXSIZE]; + UT_StubArgMetaData_t Meta[UT_STUBCONTEXT_MAXSIZE]; } UT_StubContext_t; /** @@ -311,10 +333,70 @@ uint32 UT_Stub_CopyFromLocal(UT_EntryKey_t FuncKey, const void *LocalBuffer, uin * passed as "void *" pointers to the actual stack values. The user code must * then cast them to the right type again. * + * This is now implemented as a macro which calls UT_Stub_RegisterContextWithMetaData + * to associate the name of the argument as well as the pointer. + * * \param FuncKey The stub function to entry to use. * \param Parameter Arbitrary parameter to pass. */ -void UT_Stub_RegisterContext(UT_EntryKey_t FuncKey, const void *Parameter); +#define UT_Stub_RegisterContext(FuncKey, Parameter) \ + UT_Stub_RegisterContextWithMetaData(FuncKey, #Parameter, UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED, Parameter, 0) + +/** + * Registers a single value argument into the context for the hook callback + * + * A pointer to the stack value is actually stored into the context, + * which can be dereferenced in the hook. + */ +#define UT_Stub_RegisterContextGenericArg(FuncKey, Parameter) \ + UT_Stub_RegisterContextWithMetaData(FuncKey, #Parameter, UT_STUBCONTEXT_ARG_TYPE_INDIRECT, &Parameter, sizeof(Parameter)) + +/** + * Registers a single context element for the hook callback + * + * Stubs may pass up to UT_STUBCONTEXT_MAXSIZE arguments to a user-defined + * hook function. These arguments are opaque to the stub function and generally + * passed as "void *" pointers to the actual stack values. The user code must + * then cast them to the right type again. + * + * \param FuncKey The stub function to entry to use. + * \param Name Argument name to associate with the pointer + * \param ParamType The type of parameter (direct, indirect, or unknown) + * \param ParamPtr Pointer to argument data + * \param ParamSize The size of the object pointed to, or zero if not known + */ +void UT_Stub_RegisterContextWithMetaData(UT_EntryKey_t FuncKey, const char *Name, + UT_StubContext_Arg_Type_t ParamType, const void *ParamPtr, size_t ParamSize); + +/** + * Retrieve a context argument value by name + * + * This returns a pointer to a buffer containing the value, rather than the + * value itself, even if the argument was registered originally as a direct value. + * + * If the name is not found, this logs a UT assert failure message, as it + * indicates a mismatch between the hook and stub functions with respect to argument + * names and (possibly) types that needs to be corrected. If possible, a buffer + * containing all zeros may be used as a substitute. + * + * This does not return NULL, such that the returned value can always be dereferenced. + * + * \param ContextPtr The context structure containing arguments + * \param Name Argument name to find + * \param ExpectedSize The size of the expected object type + * \returns Pointer to buffer containing the value. + */ +const void* UT_Hook_GetArgPtr(const UT_StubContext_t *ContextPtr, const char *Name, size_t ExpectedTypeSize); + +/** + * Macro which retrieves a value argument by name. + * + * This is a convenience method to easily use UT_Hook_GetArgPtr() to get the value + * associated with an argument as the correct/expected type. + * + */ +#define UT_Hook_GetArgValueByName(ContextPtr,Name,Type) \ + (*(Type const *)UT_Hook_GetArgPtr(ContextPtr,Name,sizeof(Type))) /** * Default implementation for a stub function that takes a va_list of arguments. @@ -390,6 +472,22 @@ int32 UT_DefaultStubImpl(const char *FunctionName, UT_EntryKey_t FuncKey, int32 */ #define UT_DEFAULT_IMPL_RC_ARGS(FuncName,Rc,...) UT_DefaultStubImpl(#FuncName, UT_KEY(FuncName), Rc, __VA_ARGS__) +/** + * Macro to simplify usage of the UT_DefaultStubImplWithArgs() function + * + * This function accepts a list of arguments as a va_list + */ +#define UT_DEFAULT_IMPL_VARARGS(FuncName,va) UT_DefaultStubImplWithArgs(#FuncName, UT_KEY(FuncName), 0, va) + +/** + * Macro to simplify usage of the UT_DefaultStubImplWithArgs() function + * + * This function accepts a list of arguments as a va_list and + * a nonzero default return code + */ +#define UT_DEFAULT_IMPL_RC_VARARGS(FuncName,Rc,va) UT_DefaultStubImplWithArgs(#FuncName, UT_KEY(FuncName), Rc, va) + + /** * Macro to simplify usage of the UT_DefaultStubImpl() function * diff --git a/ut_assert/src/utstubs.c b/ut_assert/src/utstubs.c index 569fd0f51..2e660f23a 100644 --- a/ut_assert/src/utstubs.c +++ b/ut_assert/src/utstubs.c @@ -591,9 +591,73 @@ void UT_SetVaHookFunction(UT_EntryKey_t FuncKey, UT_VaHookFunc_t HookFunc, void UT_DoSetHookFunction(FuncKey, Value, UserObj, true); } -void UT_Stub_RegisterContext(UT_EntryKey_t FuncKey, const void *Parameter) +const void* UT_Hook_GetArgPtr(const UT_StubContext_t *ContextPtr, const char *Name, size_t ExpectedTypeSize) +{ + uint32 i; + const void* Result; + const UT_StubArgMetaData_t *MetaPtr; + + static const union + { + uintmax_t AsInt; + void *AsPtr; + double AsFloat; + } ARG_DEFAULT_ZERO_VALUE = { 0 }; + + Result = NULL; + for (i = 0; i < ContextPtr->ArgCount; ++i) + { + MetaPtr = &ContextPtr->Meta[i]; + if (MetaPtr->Name != NULL) + { + if (strcmp(MetaPtr->Name, Name) == 0 && + (MetaPtr->Size == 0 || MetaPtr->Size == ExpectedTypeSize)) + { + if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_DIRECT) + { + Result = &ContextPtr->ArgPtr[i]; + } + else if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_INDIRECT) + { + Result = ContextPtr->ArgPtr[i]; + } + break; + } + } + } + + /* + * If no suitable result pointer was found, this means a mismatch + * between the stub and test case, such as a change in argument/parameter names. + * This is an error that should be corrected, so report it as a failure. + */ + if (Result == NULL) + { + UtAssert_Failed("Requested parameter %s of size %lu which was not provided by the stub", + Name, (unsigned long)ExpectedTypeSize); + + if (ExpectedTypeSize <= sizeof(ARG_DEFAULT_ZERO_VALUE)) + { + Result = &ARG_DEFAULT_ZERO_VALUE; + } + else + { + /* + * As the caller will likely dereference the returned pointer, should + * never return NULL. Just abort here. + */ + UtAssert_Abort("No value for parameter"); + } + } + + return Result; +} + +void UT_Stub_RegisterContextWithMetaData(UT_EntryKey_t FuncKey, const char *Name, + UT_StubContext_Arg_Type_t ParamType, const void *ParamPtr, size_t ParamSize) { UT_StubTableEntry_t *StubPtr; + UT_StubArgMetaData_t *MetaPtr; /* * First find an existing context entry for the function. @@ -616,7 +680,48 @@ void UT_Stub_RegisterContext(UT_EntryKey_t FuncKey, const void *Parameter) StubPtr->EntryType = UT_ENTRYTYPE_CALLBACK_CONTEXT; if (StubPtr->Data.Context.ArgCount < UT_STUBCONTEXT_MAXSIZE) { - StubPtr->Data.Context.ArgPtr[StubPtr->Data.Context.ArgCount] = Parameter; + StubPtr->Data.Context.ArgPtr[StubPtr->Data.Context.ArgCount] = ParamPtr; + + MetaPtr = &StubPtr->Data.Context.Meta[StubPtr->Data.Context.ArgCount]; + MetaPtr->Size = ParamSize; + MetaPtr->Type = ParamType; + + /* + * If name was specified, then trim any leading address operator (&) + * and/or whitespace, keeping only the actual name part. + */ + if (Name != NULL) + { + /* + * If the _address_ of the stack variable was actually passed in, + * the mark this as indirect (i.e. hook must dereference ArgPtr + * to get actual parameter value). Otherwise assume it as direct. + */ + MetaPtr->Name = Name; + while (*MetaPtr->Name != 0) + { + if (*MetaPtr->Name == '&') + { + /* this means its a pointer to the value, not the value itself */ + if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED) + { + MetaPtr->Type = UT_STUBCONTEXT_ARG_TYPE_INDIRECT; + } + } + else if (*MetaPtr->Name != ' ') + { + /* stop at non-whitespace */ + break; + } + ++MetaPtr->Name; + } + + if (MetaPtr->Type == UT_STUBCONTEXT_ARG_TYPE_UNSPECIFIED) + { + MetaPtr->Type = UT_STUBCONTEXT_ARG_TYPE_DIRECT; + } + + } ++StubPtr->Data.Context.ArgCount; } }