Clang Plugin Example

Fri, 27 Apr 2012 00:00:00 GMT

Clang is a compiler for the C family of programming languages (C, C++, Objective-C, etc.) based on the LLVM compiler infrastructure. Besides being an excellent compiler, it can be used for building language-aware tools. One way of doing that is to write a Clang plugin, and this page provides an example of writing such a plugin.

Update (September 2024): Updated for Clang 19.

Update (August 2016): Updated the plugin for Clang 3.9.

Getting LLVM and Clang

Steps to download and build LLVM and Clang: (For more info, see Clang - Getting Started.)

$ git clone -b release/19.x --depth=1 https://github.com/llvm/llvm-project.git
$ cd llvm-project
$ cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release \
    -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD=host llvm
$ ninja -C build

ParameterNameChecker

This example plugin can be used to check that redeclarations of a function use the same parameter names, avoiding bugs like this:

/* In the .h file: */
int divide(int numerator, int denominator);

/* In the .c file: */
int divide(int denominator, int numerator)
{
	return numerator / denominator;
}

It is common that a function is declared more than once, for example first in a .h file, and then again when it is defined in a .c file. It is not uncommon to use different names for the parameters in those cases; for example, the declaration in the header file might use more descriptive parameter names, and the definition might use shorter names. However, different names could also suggest that there is a bug, as in the example above. One way to check for this kind of parameter name mismatch is to use plugin.

To write a plugin, one must implement the PluginASTAction interface. The code below implements our example plugin: (The source is available for download here: clang-plugin-example.tar.gz.)

#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;

namespace {

// RecursiveASTVisitor does a pre-order depth-first traversal of the
// AST. We implement VisitFoo() methods for the types of nodes we are
// interested in.
class FuncDeclVisitor : public RecursiveASTVisitor<FuncDeclVisitor> {
public:
  explicit FuncDeclVisitor(DiagnosticsEngine &d) : m_diag(d) {}

  // This function gets called for each FunctionDecl node in the AST.
  // Returning true indicates that the traversal should continue.
  bool VisitFunctionDecl(const FunctionDecl *funcDecl) {
    if (const FunctionDecl *prevDecl = funcDecl->getPreviousDecl()) {
      // If one of the declarations is without prototype, we can't compare them.
      if (!funcDecl->hasPrototype() || !prevDecl->hasPrototype())
        return true;

      assert(funcDecl->getNumParams() == prevDecl->getNumParams());

      for (unsigned i = 0, e = funcDecl->getNumParams(); i != e; ++i) {
        const ParmVarDecl *paramDecl = funcDecl->getParamDecl(i);
        const ParmVarDecl *previousParamDecl = prevDecl->getParamDecl(i);

        // Ignore the case of unnamed parameters.
        if (paramDecl->getName() == "" || previousParamDecl->getName() == "")
          continue;

        if (paramDecl->getIdentifier() != previousParamDecl->getIdentifier()) {
          unsigned warn = m_diag.getCustomDiagID(DiagnosticsEngine::Warning,
              "parameter name mismatch");
          m_diag.Report(paramDecl->getLocation(), warn);

          unsigned note = m_diag.getCustomDiagID(DiagnosticsEngine::Note,
              "parameter in previous function declaration was here");
          m_diag.Report(previousParamDecl->getLocation(), note);
        }
      }
    }

    return true;
  }

private:
  DiagnosticsEngine &m_diag;
};

// An ASTConsumer is a client object that receives callbacks as the AST is
// built, and "consumes" it.
class FuncDeclConsumer : public ASTConsumer {
public:
  explicit FuncDeclConsumer(DiagnosticsEngine &d)
      : m_visitor(FuncDeclVisitor(d)) {}

  // Called by the parser for each top-level declaration group.
  // Returns true to continue parsing, or false to abort parsing.
  virtual bool HandleTopLevelDecl(DeclGroupRef dg) override {
    for (Decl *decl : dg) {
      m_visitor.TraverseDecl(decl);
    }

    return true;
  }

private:
  FuncDeclVisitor m_visitor;
};

class ParameterNameChecker : public PluginASTAction {
protected:
  // Create the ASTConsumer that will be used by this action.
  // The StringRef parameter is the current input filename (which we ignore).
  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci,
                                                 llvm::StringRef) override {
    return std::make_unique<FuncDeclConsumer>(ci.getDiagnostics());
  }

  // Parse command-line arguments. Return true if parsing succeeded, and
  // the plugin should proceed; return false otherwise.
  bool ParseArgs(const CompilerInstance&,
                 const std::vector<std::string>&) override {
    // We don't care about command-line arguments.
    return true;
  }
};

} // end namespace

// Register the PluginASTAction in the registry.
// This makes it available to be run with the '-plugin' command-line option.
static FrontendPluginRegistry::Add<ParameterNameChecker>
X("check-parameter-names", "check for parameter names mismatch");

To compile the plugin, some flags need to be specified so the compiler can find the LLVM and Clang header files, etc. The following Makefile does the job:

# The name of the plugin.
PLUGIN = ParameterNameChecker

# LLVM paths. Note: you probably need to update these.
LLVM_DIR = $(HOME)/llvm-project
LLVM_BUILD_DIR = $(LLVM_DIR)/build
CLANG_DIR = $(LLVM_DIR)/clang
CLANG = $(LLVM_BUILD_DIR)/bin/clang

# Compiler flags.
CXXFLAGS  = -I$(LLVM_DIR)/llvm/include -I$(CLANG_DIR)/include
CXXFLAGS += -I$(LLVM_BUILD_DIR)/include
CXXFLAGS += -I$(LLVM_BUILD_DIR)/tools/clang/include
CXXFLAGS += -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -Wno-long-long
CXXFLAGS += -fPIC -fvisibility-inlines-hidden
CXXFLAGS += -fno-exceptions -fno-rtti -std=c++17
CXXFLAGS += -Wall

# Linker flags.
LDFLAGS = -shared -Wl,-undefined,dynamic_lookup

$(PLUGIN).so : $(PLUGIN).o
	$(CXX) $(LDFLAGS) -o $(PLUGIN).so $(PLUGIN).o

$(PLUGIN).o : $(PLUGIN).cc
	$(CXX) $(CXXFLAGS) -c $(PLUGIN).cc -o $(PLUGIN).o

check : $(PLUGIN).so
	$(CLANG) -c -Xclang -load -Xclang ./$(PLUGIN).so \
                -Xclang -add-plugin -Xclang check-parameter-names \
                -Wno-deprecated-non-prototype test.c

clean :
	rm -fv $(PLUGIN).o $(PLUGIN).so test.o

Mac users may want to modify the file to use -dynamiclib rather than -shared in LDFLAGS, and change the file extension from .so to .dylib.

The plugin can be run like this: (-Xclang is used to prefix Clang's internal frontend options.)

$ clang -c -Xclang -load -Xclang ./ParameterNameChecker.so -Xclang -add-plugin \
                -Xclang check-parameter-names -Wno-deprecated-non-prototype \
                test.c
test.c:13:16: warning: parameter name mismatch
   13 | int divide(int denominator, int numerator)
      |                ^
test.c:10:16: note: parameter in previous function declaration was here
   10 | int divide(int numerator, int denominator);
      |                ^
test.c:13:33: warning: parameter name mismatch
   13 | int divide(int denominator, int numerator)
      |                                 ^
test.c:10:31: note: parameter in previous function declaration was here
   10 | int divide(int numerator, int denominator);
      |                               ^
2 warnings generated.