Clang Tutorial Part III: Plugin Example

Introduction to Clang Plugins

As mentioned in Part I of this tutorial, a Clang Plugin is similar to the LibTooling environment, except that it cannot reason about multiple source files (among other differences). In other words, it cannot remember details of more than one source file at a time, so keeping track of information about several source files (with something like global variables) is impossible.

That being said, there are instances when a Clang Plugin is still useful, like a syntax checker or formatting helper. As such, here’s how to build one that does the exact same thing as our LibTooling example from Part II.

Note: this is a continuation of Clang Tutorial: Part II.

Primary Differences between LibTooling and Plugins

  • Clang Plugins are located in an entirely different directory than the LibTooling programs.
  • Clang Plugins are invoked as part of your normal build process by passing a special command-line argument to the clang executable.
  • Clang Plugins can control your build process (by emitting warnings or halting it).

Setting Up a Clang Plugin

The Clang documentation already includes a good tutorial on Plugin development, so I won’t elaborate too much on it. I’ll simply explain how to convert our LibTooling code into a Plugin.

First things first, the Clang Plugin examples are in the directory llvm/tools/clang/examples/. It’s easiest just to place your Plugin there, as that’s where the official Plugin example (PrintFunctionNames) is.

The code in this example is based off of the PrintFunctionNames tutorial. Obtain the full code like so:

$ cd llvm/tools/clang/examples
$ git clone https://github.com/kevinaboos/PluginExample.git PluginExample

Let’s take a look at PluginExample.cpp. You’ll see that there are only a few differences; our implementations of the ASTConsumer and RecursiveASTVisitor classes are the same.

Plugins do not have a main() function; their entry point is through a PluginASTAction that you register with Clang.

Creating a PluginASTAction

A PluginASTAction, much like an ASTFrontendAction in LibTooling, acts as an entry point from which we can invoke our ASTConsumer. As the name implies, a PluginASTAction should only be used within the context of a Clang Plugin.

The following code should replace the ASTFrontendAction from Part II:

class PluginExampleAction : public PluginASTAction {
protected:
    // this gets called by Clang when it invokes our Plugin
    ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef file) {
        return new ExampleASTConsumer(&CI);
    }

    // implement this function if you want to parse custom cmd-line args
    bool ParseArgs(const CompilerInstance &CI, const vector<string> &args) {
        return true;
    }
};

Registering your Plugin with Clang

We also need to register our Plugin so that Clang can call it during the build process. This is simple to do:

static FrontendPluginRegistry::Add<PluginExampleAction>
       X("-example-plugin", "simple Plugin example");
This is standard procedure for all Clang Plugins and should go at the bottom of the file. Registering your Plugin requires two inputs:
  • A command-line argument string that will be used to invoke your Plugin. Above, we used “-example-plugin”, so we can invoke our Plugin with “-example-plugin” later.
  • A description of what your Plugin does (“simple Plugin example”).

Building the Plugin Example

Here is the full code for this example, including the Makefile and execution script. The Makefile for Clang Plugins is much simpler than that of LibTooling programs.

CLANG_LEVEL := ../..
LIBRARYNAME = PluginExample

SOURCES := PluginExample.cpp

LINK_LIBS_IN_SHARED = 0
SHARED_LIBRARY = 1

include $(CLANG_LEVEL)/Makefile

Running the Plugin Example

We’ll test our Plugin using the same simple test.c file from Part II. Once again, let’s utilize a script to invoke our Plugin:

#!/bin/bash

clang -Xclang -load \
      -Xclang ~/static_analysis/llvm/Debug+Asserts/lib/libPluginExample.so \
      -Xclang -plugin -Xclang -example-plugin \
      -Wall -c test.c
This may look complicated, but it’s straightforward once you break it down. First, the “-Xclang” argument just means that the following argument will be passed to Clang’s cc1 process, which is kind of like the preprocessor — cc1 is the first step before actual compilation occurs. The basic structure is:
clang -load [YourLibrary.so] -plugin -example-plugin [-Options] -c [-SourceFiles]

We simply need to put “-Xclang” in front of each argument between “clang” and “[-Options]” above. Everything else is standard GCC building syntax. Running it on multiple source files is easy as well — you simply append the name of each source file to the end of the script after “test.c”.

Conclusion

Running your Plugin should produce essentially the same output as the LibTooling example, minus the rewritten code. Clang Plugins are usually incorporated into the build process, so we don’t want to dump a lot of code to the terminal screen. As an alternative, we could print it to a new source file or overwrite the original.

This concludes my three-part Clang tutorial. As always, feel free to post questions or follow-ups in the comment section. For more advanced Clang usage, check out my other Clang-related posts in the blog archive.

Advertisements

17 comments

  1. First up, thanks for the excellent series of tutorials, they were really useful 🙂
    I had a question: any idea how I might integrate a plugin into a build system? I tried updating the CXX flags in the config file (supplying -plugin etc), but running the plugin does not produce an object file, and therefore causes the configure to fail saying “C++ compiler cannot create executables”

    1. Sure, I have gotten Clang Plugins to work with Makefiles in a variety of ways. You can use CC=clang++ and CC_FLAGS += -Xclang -plugin [...], that has worked for me. I have made it work by using CXX and CXX_FLAGS or CPP_FLAGS as well. Don’t forget to -load your plugin.so too!

      If you can’t produce an object file, then either check you -o arguments or ensure that your build system is not trying to use a format that Clang does not support, such as ELF. This is why you can’t directly build the Linux kernel with Clang using Kbuild, because of the difference in object file formats.

  2. Kevin, I sorry for another question, but 🙂 I am trying to run example plugin from clang sources, ibPrintFunctionNames.dylib, I pass following options to compiler
    -Xclang -load -Xclang /Users/…/llvm/Debug+Asserts/lib/libPrintFunctionNames.dylib -Xclang -plugin -Xclang -print-fns
    but error occurs:
    error: unable to load plugin ‘/Users/…llvm/Debug+Asserts/lib/libPrintFunctionNames.dylib’: ‘dlopen(/Users/…/llvm/Debug+Asserts/lib/libPrintFunctionNames.dylib, 9): Symbol not found: __ZN5clang11ASTConsumer21HandleInterestingDeclENS_12DeclGroupRefE Referenced from: /Users/…/llvm/Debug+Asserts/lib/libPrintFunctionNames.dylib Expected in: flat namespace in /Users/…/llvm/Debug+Asserts/lib/libPrintFunctionNames.dylib’ Command /Applications/Xcode 2.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 1
    What can be cause of this? Thank you.

    Related posts:
    http://stackoverflow.com/questions/20116475/how-can-i-specify-additional-clang-options-for-xcode-project
    http://clang-developers.42468.n3.nabble.com/Loading-clang-example-plugin-error-Symbol-not-found-ZN5clang11ASTConsumer21HandleInterestingDeclENS–td4036227.html

    1. It seems like there is some disagreement between the version of clang you have installed on your mac (I assume it’s Mac OS X because I see .dylib and Xcode) and the sources you compiled your plugin against. Did you build clang from source and the install that build on your machine? And then compile the plugin with the same source?

        1. If you’re running something through Xcode, that’s almost certainly the problem as I assume it’s using the build of Clang that is included by default on all macs (Clang is the default compiler on Mac OS X). Try running it from the command line, ensuring that your command is running the Clang you actually built.

    2. hi, firstly thx for ur tutorial for clang.. Very useful and graceful…
      but I meet with the problems: my pc is mac os & I get stuck here:
      follow the official site or ur blog’s steps, my llvm’s build dir never produce the Debug+Asserts dir && the following jobs never be finished…..
      looking forward to ur reply, thx again….

  3. Hi,
    the command line gives me the awaited standard output, but no .o file was generated, is it normal? When I try without the -c, option, the linker tells me it can’t find the related object file. Is it normal no object file was generated??

    Thanks for the tutorial 🙂

    1. To run your Clang plugin, you don’t actually need to complete all the compilation steps and generate an object file. You should still be able to, though.

    2. Try using “-add-plugin” instead of “-plugin” (as per Reto’s comment). Using “-add-plugin” I get an object file.

      From “clang -cc1 –help”:
      -add-plugin Use the named plugin action in addition to the default action
      -plugin Use the named plugin action instead of the default action

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s