alexrp's blog

ramblings usually related to software

Clang's Static Analyzer and Automake

Writing software in C and C++ is generally a somewhat painful task. Compilers have long tried to make it less so by analyzing input code and emitting useful warnings when they spot something that doesn’t seem quite right, such as using an uninitialized variable. This is great, but not quite enough. Clang’s static analyzer performs abstract interpretation of the input code in order to find bugs that can’t be found in the information that is readily available to the compiler during compilation. This includes, for example, bad locking patterns in multithreaded code and memory bugs such as leaks, double-free, use-after-free, etc.

Unfortunately, the scan-build tool is – at least in my experience – rather flaky and tends to not actually work with most Autotools-based build systems. So I concluded that invoking clang --analyze manually would be a better idea. This, however, requires a little modification of Makefile.am in projects that use Automake.

Let’s suppose we’re building a library libfoo like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
lib_LTLIBRARIES = libfoo.la

libfoo_la_SOURCES = \
  foo.c \
  utils.c

libfoo_la_LDFLAGS = -export-symbols-regex "^foo_"

libfooincludedir = $(includedir)/foo

libfooinclude_HEADERS = \
  foo.h \
  utils.h

(I’m not going to explain what all that means. Consult the Automake manual if you don’t know. In any case, your project will likely contain something like this.)

The Clang static analyzer generates .plist files containing the results of the analysis. So we add this to map our source files to .plist files:

1
2
3
4
analyze_srcs = $(filter %.c, $(libfoo_la_SOURCES))
analyze_plists = $(analyze_srcs:%.c=%.plist)

MOSTLYCLEANFILES = $(analyze_plists)

The assignment to MOSTLYCLEANFILES is needed so that these files are cleaned up when we invoke make clean or make mostlyclean. Why MOSTLYCLEANFILES instead of CLEANFILES? See the manual.

Now we write the rule to actually perform the analysis and save the .plist files. This rule is added to the all-local target so that Automake executes it as part of a normal build.

1
2
3
4
5
$(analyze_plists): %.plist: %.c
  @echo "  CCSA  " $@
  @$(COMPILE) --analyze $< -o $@

all-local: $(analyze_plists)

If you’d rather not run the static analyzer as part of the normal build, you can omit the last line in that snippet and instead use this:

1
2
3
analyze: $(analyze_plists)

.PHONY: analyze

This lets you invoke make analyze to run the static analyzer instead.

Now, with the above code in place, we can run make clean all and get this:

1
2
3
4
5
6
7
# ... snip ...
  CC     libfoo_la-foo.lo
  CC     libfoo_la-utils.lo
  CCLD   libfoo.la
  CCSA   foo.plist
  CCSA   utils.plist
# ... snip ...

Let’s try putting some (contrived) broken code in utils.c:

1
2
3
4
5
6
7
8
#include <stdlib.h>

static void foo(void)
{
    void *p = malloc(1);
    free(p);
    free(p);
}

Running make will now give us:

1
2
3
4
5
6
7
# ... snip ...
  CCSA   foo.plist
utils.c:7:5: warning: Attempt to free released memory
    free(p);
    ^~~~~~~
1 warning generated.
# ... snip ...

And that’s all it takes to integrate the Clang static analyzer in a project using Automake. I really like the idea of static analysis being part of the regular build process. Helps catch nasty bugs as early as possible.