Home > Technology > Add new warnings to GCC with Python

Add new warnings to GCC with Python

January 23, 2014

I found myself getting annoyed lately with a certain C++ anti-pattern: functions which take string parameters by value unnecessarily.  The pattern itself isn’t a huge sin, and can have legitimate uses, but often functions would be just as happy with a string const& parameter, so copying it is a waste.  No matter how the optimized string copy is, it won’t be better than a simple reference.  Then throw more complex classes in the mix, and it would be good have a tool or diagnostic to tell when things are copied unnecessarily.

My first thought was to try this with elfutils libdw, or perhaps Dyninst SymtabAPI.  It shouldn’t be that hard to iterate debuginfo for all the functions in a binary, and scan their parameters for direct class or struct types.  I may still try this, actually, just as a case study for relatively simple example code.  However, one of my coworkers suggested this idea would also work as a GCC plugin, so I tried it with gcc-python-plugin and found it embarrassingly easy.  Voila!

import gcc

def on_pass_execution(p, fn):
    # This pass is called fairly early on, per-function, after the
    # CFG has been built.  Skip every other pass.
    if p.name != '*warn_function_return':
        return

    # I don't want notices for system headers.
    # (is data like -isystem available?)
    if fn.decl.location.file.startswith('/usr/'):
        return

    for parm, parmtype in zip(fn.decl.arguments,
                              fn.decl.type.argument_types):
        if type(parmtype) == gcc.RecordType:
            gcc.inform(parm.location,
                       'parameter type is not trivial')

gcc.register_callback(gcc.PLUGIN_PASS_EXECUTION,
                      on_pass_execution)

I started with the hello-world example, which already shows how to hook every function and extract more info than I even need.  You’ll see that my case is even shorter in code, just checking the rough parameter type and issuing a notice.  In practice it looks like this:

$ cat test.cc
#include <string>
size_t len(std::string s) { return s.size(); }

$ gcc-with-python3 script.py -c test.cc
test.cc: In function ‘size_t len(std::string)’:
test.cc:2:24: note: parameter type is not trivial
 size_t len(std::string s) { return s.size(); }
                        ^

I got this working with my whole build just by tweaking make options:

make CXXLD=g++ CXX="gcc-with-python3 /path/to/script.py"

Pull that into Vim quickfix, and I’m now easily browsing through every instance of non-trivial parameter copies in my code!

Part of the reason this was so easy was that I just flag every direct class/struct parameter, regardless of whether an option like const& would fit. This might be a little more intelligent if it scanned the CFG to see if the parameter really could be constant. It could also relax a bit on what’s flagged, perhaps just types for which std::is_trivially_copyable returns false in C++11.  But for just scratching an itch, I’m amazed at how quick this simple plugin was to write.

  1. January 24, 2014 at 7:38 AM

    > # I don’t want notices for system headers.
    > # (is data like -isystem available?)
    gcc has that as a flag internally, on locations [1], but the plugin doesn’t yet expose that on gcc.Location instances. I’ll see if I can add it.

    [1] “in_system_header_at(LOC)” within input.h

  2. January 24, 2014 at 7:38 AM

    > # (is data like -isystem available?)

    It’s available internally but I don’t think the Python plugin exposes it. I think gcc.Location could use a few new attributes; at least “in_system_header” and perhaps some related to macro expansion information.

  3. January 24, 2014 at 8:22 AM

    I’ve added an “in_system_header” boolean attribute to gcc.Location as
    https://git.fedorahosted.org/cgit/gcc-python-plugin.git/commit/?id=58968fff3b23fcca0641ce5ad5fbc0c7a19242f4

    • January 24, 2014 at 9:16 AM

      Nice, thanks!

  1. No trackbacks yet.
Comments are closed.