6

I am working on a small library where I don't want to deal with allocation errors everywhere they might pop up, so I've written a wrapper around malloc() that terminates the program (for now) if an error occurs.

void *cstr_malloc(size_t size)
{
    void *buf = malloc(size);
    if (!buf)
    {
        fprintf(stderr, "Allocation error, terminating\n");
        exit(2);
    }
    return buf;
}

Nothing fancy there. Now, however, I noticed that the clang static analyzer, in Xcode at least, doesn't catch obvious leaks that it would have done before.

If I do something like this:

void foo(void)
{
    void *p = malloc(100);
}

it would naturally inform me that p would likely leak memory.

However, with

void foo(void)
{
    void *p = malloc(100);
    void *q = cstr_malloc(100);
}

it only reports that p, but not q, is leaking.

That makes sense; it can't know everywhere the program might allocate memory and still analyse it in a reasonable time. It handles my allocator fine if I inline it, so there it can see it, but otherwise, it doesn't.

Is there any way to tell the analyser that I have a function that returns freshly allocated memory? Some attribute, like __attribute__((malloc)) or similar?

I can, of course, just inline the function, but I have a bunch of other functions that similarly allocate memory, and it would be problematic to inline all of them.

Thomas Mailund
  • 1,674
  • 10
  • 16
  • @P.P Doesn't seem to work though: https://godbolt.org/z/9o19esbhG – user17732522 Feb 23 '22 at 09:35
  • 1
    I did try __attribute__((malloc)) but it work for me. Of course, there could easily be something else wrong and that is why I am not seeing an effect. But the attribute alone didn't do the trick while inlining the allocator did... – Thomas Mailund Feb 23 '22 at 09:36
  • Hmm, @P.P on Godbolt it works for me (but not on my own machine). Should I understand it such that it doesn't work for you on Godbolt? Then it might be a configuration issue I need to figure out. EDIT: nope, I misread Godbolt. It doesn't work there either. – Thomas Mailund Feb 23 '22 at 09:38
  • @StoryTeller-UnslanderMonica I only see a warning for the allocated `p`, not the (equally allocated) `q`. It doesn't see (and here, of course, can't see) that `q` is also allocated memory. – Thomas Mailund Feb 23 '22 at 09:42
  • Yeah, never mind that comment. The warning text mentions `p`, but the code highlight under it is on `q`. Confusing stuff. – StoryTeller - Unslander Monica Feb 23 '22 at 09:43
  • @StoryTeller-UnslanderMonica that's also how I misread it the first time. – Thomas Mailund Feb 23 '22 at 09:47

1 Answers1

2

After some hours of googling, I found what at least looks like a solution. The trick is indeed to use an attribute, but not __attribute__((malloc)).

The attribute I needed was ownership_returns(malloc). Using that, I get warnings, at least for this function.

__attribute__((ownership_returns(malloc)))
void *cstr_malloc(size_t size);

See https://godbolt.org/z/MEfvThKnW

Unfortunately, my joy was short-lived. If I use ownership_returns(malloc), then the analyser will assume that the memory is also uninitialised, which it isn't always. I have no idea how to hint that I return memory that must be freed, but not uninitialised memory.

Since clang can figure out that this is the case for calloc(), there might be a way to do it for my functions as well, but I haven't found it.

Here is an example of how clang deals with memory returned from calloc() and what it thinks of what comes from my own function: https://godbolt.org/z/P45o691Tx

Thomas Mailund
  • 1,674
  • 10
  • 16