Introduction

Clang Static Analysis (CSA) does static analysis on the source code level IR, the Clang AST, rather than LLVM low level IR.

Prerequisite

LLVM System Configuration

Example (Clang Type Analysis skeleton code)

The full example code is here but you may want to know the details of the code tree.

clang-type-analysis.zip

Create a project

For the Clion project setup, please refer to the pass document:

Hello_World Pass (Take CLion on Mac as an Example)

Change CMakeLists.txt and add a checker

Change the CMakeLists.txt as follows:

cmake_minimum_required(VERSION 2.8.8)

#Project declaration
project(ClangTypeAnalysis DESCRIPTION "libClangTypeAnalysis")

find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)

#We might only have llvm-config-X.Y
find_program (LLVM_CONFIG_WITH_NUMBER "llvm-config-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}")
 if(LLVM_CONFIG_WITH_NUMBER MATCHES "NOTFOUND")
   find_program (REAL_LLVM_CONFIG "llvm-config")
 else()
   set(REAL_LLVM_CONFIG ${LLVM_CONFIG_WITH_NUMBER})
 endif()
message(STATUS "Found: ${REAL_LLVM_CONFIG}")

find_package(LLVM REQUIRED CONFIG)

message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

llvm_map_components_to_libnames(llvm_libs support core irreader bitwriter TransformUtils option)

#get compile flags
execute_process(COMMAND "${REAL_LLVM_CONFIG}" --cxxflags  OUTPUT_VARIABLE CPP_LLVM_CONFIG OUTPUT_STRIP_TRAILING_WHITESPACE)

#get system lib link flags
execute_process(COMMAND "${REAL_LLVM_CONFIG}" --system-libs OUTPUT_VARIABLE SYSTEM_LIBS_LLVM_CONFIG OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND "${REAL_LLVM_CONFIG}" --includedir OUTPUT_VARIABLE LLVM_CONFIG_INCLUDE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND "${REAL_LLVM_CONFIG}" --src-root OUTPUT_VARIABLE LLVM_SRC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND "${REAL_LLVM_CONFIG}" --obj-root OUTPUT_VARIABLE LLVM_OBJ_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
include_directories(SYSTEM ${LLVM_SRC_DIR}/tools/clang/include SYSTEM ${LLVM_OBJ_DIR}/tools/clang/include SYSTEM ${LLVM_CONFIG_INCLUDE_DIR})
message(STATUS "include dir is ${LLVM_CONFIG_INCLUDE_DIR} ${LLVM_OBJ_DIR}/tools/clang/include ${LLVM_CONFIG_INCLUDE_DIR}")

execute_process(COMMAND "${REAL_LLVM_CONFIG}" --libdir OUTPUT_VARIABLE LLVM_CONFIG_LINk_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
link_directories( ${LLVM_CONFIG_LINk_DIR} )
message(STATUS "link dir is ${LLVM_CONFIG_LINk_DIR}")

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${CPP_LLVM_CONFIG}")

add_library(ClangTypeAnalysis SHARED ClangTypeAnalysis.cpp)

set (LLVM_LIBS
    ${llvm_libs}
)

set(CLANG_LIBS
  clangBasic
  clangEdit
  clangFrontend
  clangStaticAnalyzerFrontend
  clangStaticAnalyzerCore
  clangStaticAnalyzerCheckers
  clangSerialization
  clangSema
  clangAnalysis
  clangCrossTU
  clangRewriteFrontend
  clangRewrite
  clangLex
  clangAST
  clangASTMatchers
  clangDynamicASTMatchers
  clangIndex
  clangDriver
  clangParse
  )

if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
    set(START_GROUP "-Wl,--start-group")
    set(END_GROUP "-Wl,--end-group")
else()
    set(START_GROUP "")
    set(END_GROUP "")
endif()

target_link_libraries(ClangTypeAnalysis
    ${START_GROUP}
    ${CLANG_LIBS}
    ${LLVM_LIBS}
    ${END_GROUP}
    ${SYSTEM_LIBS_LLVM_CONFIG})

Note that the CMakeLists.txt defines the Clang libraries to use in Clang Static Analysis checker development. Now we need to create a checker cpp file since CMakeLists.txt specifies it as ClangTypeAnalysis.cpp .

#include <iostream>
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"

using namespace clang;
using namespace ento;

namespace {
class ClangTypeAnalysis : public Checker < check::PreStmt<Stmt>,
        check::ASTDecl<Decl>,
        check::EndAnalysis > {
  mutable std::unique_ptr<BugType> BT;

public:
  void checkPreStmt(const Stmt *ST, CheckerContext &C) const;
  void checkASTDecl(const Decl *D,
                      AnalysisManager &Mgr,
                      BugReporter &BR) const;
  void checkEndAnalysis(ExplodedGraph &G,
                           BugReporter &BR, ExprEngine &Eng) const;
};
} // end anonymous namespace

void ClangTypeAnalysis::checkPreStmt(const Stmt *ST, CheckerContext &C) const {
    SourceLocation Loc = ST->getBeginLoc();
    auto& SM = C.getSourceManager();

    if ((Loc.isInvalid() || SM.isInSystemHeader(Loc) || SM.isInExternCSystemHeader(Loc) || SM.isInSystemMacro(Loc)) && !SM.isWrittenInMainFile(Loc)) {
        return;
    }
    // do something with stmt
}

void ClangTypeAnalysis::checkASTDecl(const Decl *D,
                                     AnalysisManager &Mgr,
                                     BugReporter &BR) const {
    SourceLocation Loc = D->getBeginLoc();
    auto& SM = Mgr.getSourceManager();

    if ((Loc.isInvalid() || SM.isInSystemHeader(Loc) || SM.isInExternCSystemHeader(Loc) || SM.isInSystemMacro(Loc)) && !SM.isWrittenInMainFile(Loc)) {
        return;
    }
    // do something with decl
}

void ClangTypeAnalysis::checkEndAnalysis(ExplodedGraph &G,
                                         BugReporter &BR, ExprEngine &Eng) const {
    // evaluate at the end of analysis
}

// Register plugin!
extern "C"
void clang_registerCheckers (CheckerRegistry &registry) {
  registry.addChecker<ClangTypeAnalysis>("homework.ClangTypeAnalysis", "Type Analysis", "nodoc");
}

extern "C"
const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;

The class PreStmt and ASTDecl are checker callback classes that perform operations on each Decl and Stmt node of the Clang AST. The endAnalysis is a class that does evaluation when all the checks have finished, which is useful to print out statistics of the analysis result. The registry method specifies the name of the checker in the command line, which is "homework.ClangTypeAnalysis" in this example.

Build and run

  1. Build the project. The output shall be similar to this:

  2. Run the checker manually.

    The result should be similar as this (because we almost do nothing but print out the verbose message):