0

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]
      ^
mark
  • 7,381
  • 5
  • 36
  • 61

1 Answers1

1

I am a Cppcheck developer.

I am not aware of a builtin configuration option or possible workaround. Not sure what to recommend.

a hack would be to copy the code to some temporary file and create a script (sed/python/..) that rewrites your code:

Status status = osMemAlloc((void**)& myPtr, sizeof(SomeStruct));


into:

Status status = success; myPtr = malloc(sizeof(SomeStruct));

A more proper solution would be to add an improvement in Cppcheck. If you would be interested to contribute a fix feel free to look at this. In my humble opinion the code is not super complex, there are no required dependencies so if you have a C++ compiler that can compile a hello world program you should be able to compile Cppcheck also. Source code: https://github.com/danmar/cppcheck

Daniel Marjamäki
  • 2,907
  • 15
  • 16
  • Thanks for your response - I believe there is a bug in CPpCheck with casting to (void**). I have added some more information above. – mark Dec 15 '20 at 12:52
  • Sorry, I somehow misunderstood your point at first. I have created this ticket: https://trac.cppcheck.net/ticket/10047 hope you think that looks ok. It should be fixed soonish.. but feel free to look at it if you want a quick solution. :-) EDIT: If you solve it, please provide a github pull request. – Daniel Marjamäki Dec 17 '20 at 14:51