
Clang Plugin Example
(27 April 2012)
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.