I am working on a codebase that has a vast amount of legacy code and I am trying to update CppCheck to the latest version.
This code relies on a number of custom malloc
style functions with the following signature:
Status osMemAlloc(void** ptr, size_t numBytes);
The signature of this function means the CppCheck does not deduce that the function osMemAlloc
is allocating memory for ptr
meaning that it flags {nullPointer} Null pointer dereference errors in code such as:
SomeStruct* myPtr = NULL;
Status status = osMemAlloc((void**)& myPtr, sizeof(SomeStruct));
A
assert(status == SUCCESS_E);
pT
myPtr->param1 = val1; // (error) {nullPointer} Null pointer dereference
myPtr-> param2 = val2; // (error) {nullPointer} Null pointer dereference
How do I tell CppCheck that the call to osMemAlloc((void**)& myPtr, sizeof(SomeStruct))
is allocating memory for myPtr
.
Changing the signature of the function to something more natural is not an option as the code base is huge & very flakey with no proper test harnesses.
Further Info
I have tried playing with various macro definitions (expanded manually) which replace the call for osMemAlloc()
as a way to resolve this and believe there is a bug in CppCHeck where a cast is causing CppCheck to miss the assignment of allocated memory to the pointer.
The original call site for the allocation has the form:
if (osMemAlloc((void **)&device,sizeof(DeviceInfo)) == OS_OK)
CPPCheck is happy with the osMemAlloc being replaced with a macro which would expand as:
if ((((*(&device)=cppcheck_HeapAlloc(sizeof(Device))) != NULL)
? SUCCESS_E : ERROR_E) == SUCCESS_E)
Unfortunately GCC is not happy with that call as it requires a cast to (void**) to match the original function. If that cast is added the code becomes:
if ((((*((void**)(&device))=cppcheck_HeapAlloc(sizeof(Device))) != NULL)
? OS_OK : OS_ERROR) == OS_OK)
Which causes CPPCheck to fail. I believe that the cast is enough to cause CppCheck to fail to call Token::addValue()
in cppcheck/lib/token.cpp
Full Example code that fails in CppCheck:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
typedef struct Device
{
uint32_t param1;
uint32_t param2;
} Device;
enum Status
{
OS_OK = 0,
OS_ERROR = 1
};
// Macro used to hide/replace OS Abstraction of Malloc in legacy code.
// The real code forwards to a function withe following signature:
// Status osMemAlloc(void** ptr, size_t sx);
#define osMemAlloc(ptr, sz) ((*(ptr)=malloc(sz)) != NULL ? OS_OK : OS_ERROR)
int main()
{
Device* device1 = NULL;
Device* device2 = NULL;
/// This call / expansion of the macro without the casts is fine,
if ((((*(&device1)=malloc(sizeof(Device))) != NULL) ? OS_OK : OS_ERROR) == OS_OK)
{
device1->param1 = 10;
device1->param2 = 20;
}
/// Note the cast is ncessary when the real function is called for C++
//if ((((*((void**)&device2)=malloc(sizeof(Device))) != NULL) ? OS_OK : OS_ERROR) == OS_OK)
if (osMemAlloc((void**)&device2, sizeof(Device)) == OS_OK)
{
device2->param1 = 10; // error: Null pointer dereference: device2 [nullPointer]
device2->param2 = 20; // error: Null pointer dereference: device2 [nullPointer]
}
printf("Done\n");
free(device1);
free(device2);
}
The Output from CppCheck
cppcheck.exe cppcheck-error.c
Checking cppcheck-error.c ...
cppcheck-error.c:39:7: error: Null pointer dereference: device2 [nullPointer]
device2->param1 = 10; // error: Null pointer dereference: device2 [nullPointer]
^
cppcheck-error.c:26:22: note: Assignment 'device2=NULL', assigned value is 0
Device* device2 = NULL;
^
cppcheck-error.c:39:7: note: Null pointer dereference
device2->param1 = 10; // error: Null pointer dereference: device2 [nullPointer]
^
cppcheck-error.c:40:7: error: Null pointer dereference: device2 [nullPointer]
device2->param2 = 20; // error: Null pointer dereference: device2 [nullPointer]
^
cppcheck-error.c:26:22: note: Assignment 'device2=NULL', assigned value is 0
Device* device2 = NULL;
^
cppcheck-error.c:40:7: note: Null pointer dereference
device2->param2 = 20; // error: Null pointer dereference: device2 [nullPointer]
^