This document describes the C++ Clang Static Analyzer checkers that form the core analysis layer of Futag. These checkers extract function signatures, type information, and usage contexts from C/C++ library source code, producing JSON output consumed by the Python generator layer.
Futag uses two custom Clang checkers integrated into the LLVM Static Analyzer:
- FutagAnalyzer -- Analyzes library source code to extract function declarations, type definitions (structs, enums, typedefs), and call contexts.
- FutagConsumerAnalyzer -- Analyzes consumer programs that use the library, extracting usage contexts: how library functions are called, what arguments are passed, and in what order.
Library Source Code
|
v
[FutagAnalyzer] (Clang checker, runs via scan-build)
|
v
JSON analysis files:
.declaration-*.json (function signatures)
.context-*.json (call contexts)
.types-info-*.json (enums, records, typedefs)
.file-info-*.json (includes, compiler options)
|
v
futag-analysis-result.json (aggregated by Python preprocessor.py)
|
+---> [Python Generator] (generates fuzz targets)
|
v
futag-4consumer.json (subset for consumer analysis)
|
v
Consumer Source Code + futag-4consumer.json
|
v
[FutagConsumerAnalyzer] (Clang checker)
|
v
_funcname_varname_blocks.json (per-context files)
|
v
futag-contexts.json (aggregated by Python preprocessor.py)
|
v
[Python ContextGenerator] (generates context-based fuzz targets)
Checkers are defined in analyzers/checkers/include/Checkers.td using Clang's TableGen format:
let ParentPackage = Futag in {
def FutagAnalyzer : Checker<"FutagAnalyzer">,
CheckerOptions<[
CmdLineOption<String, "report_dir",
"Absolute path to the directory, where to write results",
"/tmp/futag-function-analyzer-reports/", Released>,
]>;
def FutagConsumerAnalyzer : Checker<"FutagConsumerAnalyzer">,
CheckerOptions<[
CmdLineOption<String, "consumer_report_path", ...>,
CmdLineOption<String, "db_filepath", ...>,
]>;
}
Invoked via scan-build:
scan-build -enable-checker futag.FutagAnalyzer \
-analyzer-config futag.FutagAnalyzer:report_dir=/path/to/output \
makeSource: analyzers/checkers/lib/FutagAnalyzer.cpp (+ LLVM14/18 variants)
FutagAnalyzer : public Checker<check::ASTDecl<TranslationUnitDecl>>
The checker hooks into Clang's ASTDecl callback, receiving the entire translation unit AST for each compiled file.
-
registerFutagAnalyzer()-- Called by Clang during checker setup. Creates the checker instance, reads thereport_diroption, and generates random filenames for 4 output JSON files. -
checkASTDecl(TranslationUnitDecl)-- Entry point called once per translation unit.- Collects include file information from
SourceManager - Creates a
RecursiveASTVisitor(inner classLocalVisitor) - Traverses the entire AST
- Collects include file information from
-
Visitor callbacks:
VisitFunction(FunctionDecl)-- For each function: computes ODR hash, extracts signature metadata, determines parameter usage patterns, finds internal call contextsVisitRecord(RecordDecl)-- For structs/classes/unions: extracts fields with type infoVisitTypedef(TypedefDecl)-- For typedefs: extracts underlying type and links to source typeVisitEnum(EnumDecl)-- For enums: extracts enum values
-
Destructor -- Writes all collected JSON data to the output files.
| Method | Purpose |
|---|---|
CollectBasicFunctionInfo() |
Extracts: ODR hash, name, location, return type, parameters, function type, access type, storage class, is_simple flag |
CollectAdvancedFunctionInfo() |
Uses AST matchers to find call expressions within function body; records which functions call which |
DetermineArgUsageInAST() |
Classifies how each parameter is used (file path, size field, C string, etc.) via ArgumentsUsage |
WriteInfoToTheFile() |
Writes JSON data to a file, merging with existing content if the file already exists |
{
"<odr_hash>": {
"name": "func_name",
"qname": "namespace::func_name",
"hash": "12345678",
"is_simple": true,
"func_type": 4,
"access_type": 3,
"storage_class": 0,
"parent_hash": "",
"return_type": { "type_name": "int", "gen_type": 0 },
"gen_return_type": [{ "gen_type": 0, "type_name": "int", ... }],
"params": [
{
"param_name": "buf",
"param_type": "char *",
"param_usage": "C_STRING",
"gen_list": [{ "gen_type": 1, "type_name": "char *", ... }]
}
],
"fuzz_it": true,
"call_contexts": [...],
"location": "/path/to/file.c:42"
}
}| Value | Name | Meaning |
|---|---|---|
| 0 | _FUNC_CXXMETHOD |
C++ class method |
| 1 | _FUNC_CONSTRUCTOR |
C++ constructor |
| 2 | _FUNC_DEFAULT_CONSTRUCTOR |
C++ default constructor |
| 3 | _FUNC_DESTRUCTOR |
C++ destructor |
| 4 | _FUNC_GLOBAL |
Global C function |
| 5 | _FUNC_STATIC |
Static function |
Source: analyzers/checkers/lib/FutagConsumerAnalyzer.cpp (+ LLVM14/18 variants)
Analyzes consumer programs to extract usage contexts -- how library functions are actually called in real code. This produces structured data that the Python ContextGenerator uses to create more realistic fuzz targets.
Requires analysis_jdb -- the JSON database from a prior FutagAnalyzer run (loaded via the db_filepath checker option).
-
registerFutagConsumerAnalyzer()-- Creates checker, loads JSON database fromdb_filepath, sets output path. -
checkASTDecl(TranslationUnitDecl)-- Entry point. Collects includes, then traverses AST usingRecursiveASTVisitor. -
AnalyzeVisitedFunctionDecl(FunctionDecl)-- Core analysis for each function:Phase 1: Match variable initializations (AST matching)
- Uses
FutagMatchInitCallExprCBto find patterns likeT *var = library_func(...) - Cross-references matched function names against
analysis_jdb - Produces
matched_init_contexts: map of (variable -> initializing call expression)
Phase 2: Build CFG and enumerate paths
- Builds the Control Flow Graph (CFG) for the function
- Functions with >30 CFG blocks are skipped (configurable via
MAX_CFG_BLOCKS) FindAllCFGPaths()recursively enumerates all paths through the CFG
Phase 3: For each CFG path, extract ordered call sequence
- Maps each init_call to its CFG block position within the path
- For each init_call's arguments:
SearchVarDeclInBlock()traces variable definitions backward through preceding CFG blocks SearchModifyingCallExprInBlock()finds subsequent calls that modify the initialized variable- Writes the context (init_calls + modifying_calls + cfg_blocks) to a JSON file
- Uses
The analyzer builds a Control Flow Graph for each consumer function and enumerates all execution paths:
FindAllCFGPaths(cfg, entry_block, graph, current_path, all_paths)
For each successor of current block:
If successor already in path: skip (avoid cycles)
If successor is exit block: save path
Else: recurse with successor appended to path
Each context file (_funcname_varname_blocks.json) contains:
{
"cfg_blocks": [5, 3, 2, 1, 0],
"init_calls": {
"conn": {
"name": "curl_easy_init",
"qname": "curl_easy_init",
"cfg_block_ID": 3,
"str_stmt": "curl_easy_init()",
"file": "consumer.c",
"line": 15,
"col": 20,
"args": []
}
},
"modifying_calls": [
{
"name": "curl_easy_setopt",
"qname": "curl_easy_setopt",
"cfg_block_ID": 2,
"str_stmt": "curl_easy_setopt(conn, CURLOPT_URL, url)",
"file": "consumer.c",
"line": 16,
"col": 5,
"args": [
{"init_type": "ArgVarRef", "value": "conn"},
{"init_type": "ArgConstValue", "value": "10002"},
{"init_type": "ArgVarRef", "value": "url"}
]
}
]
}Source: analyzers/clang-patches/include/Futag/Basic.h, analyzers/clang-patches/lib/Futag/Basic.cpp
Futag decomposes C/C++ types into a chain of GenTypeInfo structs:
struct GenTypeInfo {
FutagGenType gen_type; // Classification (F_BUILTIN, F_CSTRING, ...)
std::string base_type_name; // Unqualified/pointee/element type
std::string type_name; // Current qualified type
std::string local_qualifier; // const, volatile, restrict
uint64_t length; // Array length (0 if not array)
};| Value | Name | Example |
|---|---|---|
| 0 | F_BUILTIN |
int, float, double |
| 1 | F_CSTRING |
char *, const char * |
| 2 | F_WSTRING |
wchar_t * |
| 3 | F_CXXSTRING |
std::string |
| 4 | F_ENUM |
Any enum type |
| 5 | F_ARRAY |
int[10] |
| 6 | F_VOIDP |
void * |
| 7 | F_QUALIFIER |
const/volatile wrapper |
| 8 | F_POINTER |
Any pointer type |
| 9 | F_STRUCT |
struct types |
| 10 | F_UNION |
union types |
| 11 | F_CLASS |
C++ class types |
The Python sysmsg.py defines matching constants:
| C++ | Python |
|---|---|
F_BUILTIN (0) |
GEN_BUILTIN (0) |
F_CSTRING (1) |
GEN_CSTRING (1) |
F_STRUCT (9) |
GEN_STRUCT (9) |
| ... | ... |
getGenType(QualType)-- Recursively decomposes a type into a vector ofGenTypeInfogetGenField(QualType)-- Similar but for struct field typesisSimpleType(QualType)-- Returns true if the type (or its pointee) is a builtin typeisSimpleFunction(FunctionDecl)-- Returns true if all parameters are simple types
| Callback Class | Pattern Matched | Purpose |
|---|---|---|
FutagMatchCallExprCallBack |
callExpr(callee(functionDecl(...))) |
Finds function calls within a function body; records caller/callee relationship |
FutagArgUsageDeterminer |
callExpr(hasAnyArgument(hasDescendant(declRefExpr(to(varDecl(hasName(...))))))) |
Determines how a parameter is used (file path, size, string, etc.) |
FutagMatchVarDeclArgCallBack |
Variable declaration matching | Tracks variable initializations |
FutagMatchBinOperatorArgCallBack |
Assignment operator matching | Tracks variable assignments |
| Callback Class | Pattern Matched | Purpose |
|---|---|---|
FutagMatchInitCallExprCB |
varDecl(hasInitializer(callExpr(...))) |
Finds T *var = func(...) patterns |
FutagMatchDefCallExprCB |
binaryOperator(isAssignment, hasRHS(callExpr(...))) |
Finds var = func(...) patterns |
FutagMatchModCallExprCB |
callExpr(hasDescendant(declRefExpr(to(varDecl(hasName(...)))))) |
Finds calls that reference a specific variable |
| Function | Purpose |
|---|---|
GetCallExprInfo() |
Extracts detailed info from a CallExpr: function name, arguments (with types), location, CFG block |
SearchVarDeclInBlock() |
Searches a CFG block for variable definitions that use library function calls |
SearchModifyingCallExprInBlock() |
Searches a CFG block for calls that reference a specific variable |
HandleLiterals() |
Converts Expr literal types to FutagInitArg with appropriate type classification |
All files are written to the report_dir with random suffixes to avoid collisions during parallel compilation.
| File Pattern | Content |
|---|---|
.declaration-<rand>.futag-analyzer.json |
Function declarations keyed by ODR hash |
.context-<rand>.futag-analyzer.json |
Function call contexts (which functions call which) |
.types-info-<rand>.futag-analyzer.json |
{"enums": [...], "records": [...], "typedefs": [...]} |
.file-info-<rand>.futag-analyzer.json |
{"file": "path", "includes": [...], "compiler_opts": "..."} |
Written to consumer_report_path:
| File Pattern | Content |
|---|---|
_<funcname>_<varname>_<blocks>.json |
Per-context file with {cfg_blocks, init_calls, modifying_calls} |
The project maintains version-specific copies of files for LLVM 14 and 18 compatibility:
| Base File | LLVM 14 | LLVM 18 |
|---|---|---|
FutagAnalyzer.cpp |
FutagAnalyzer14.cpp |
FutagAnalyzer18.cpp |
FutagConsumerAnalyzer.cpp |
FutagConsumerAnalyzer14.cpp |
FutagConsumerAnalyzer18.cpp |
ASTMatchFinder.h |
ASTMatchFinder14.h |
ASTMatchFinder18.h |
CMakeLists.txt |
CMakeLists14.txt |
CMakeLists18.txt |
Checkers.td |
Checkers14.td |
Checkers18.td |
Key API differences:
- LLVM 14:
SourceManager::fileinfo_iteratoryields pointers (it->first->getName()) - LLVM 18+:
SourceManager::fileinfo_iteratoryields objects (it->first.getName())
The base file always matches the LLVM 18 version. The build.sh script selects the correct version-specific files during LLVM compilation.
Checkers are compiled into Clang's clangStaticAnalyzerCheckers library:
# analyzers/checkers/lib/CMakeLists.txt
add_clang_library(clangStaticAnalyzerCheckers
...
FutagAnalyzer.cpp
FutagConsumerAnalyzer.cpp
...
)Supporting code is built as FutagLib:
# analyzers/clang-patches/lib/Futag/CMakeLists.txt
add_clang_library(FutagLib
Basic.cpp
MatchFinder.cpp
ConsumerFinder.cpp
)- nlohmann/json (
vendors/json-3.10.5/) -- JSON serialization for all output - Clang AST/ASTMatchers -- AST traversal and pattern matching
- Clang Analysis/CFG -- Control flow graph construction (Consumer only)