-
Notifications
You must be signed in to change notification settings - Fork 0
Adding a New Check
This guide walks through adding a new diagnostic check to the analyzer.
Add a new entry to DescriptiveErrorCode in include/StackUsageAnalyzer.hpp:
enum class DescriptiveErrorCode
{
// ... existing codes ...
YourNewCheck = 17 // next available number
};Update the EnumTraits<DescriptiveErrorCode> specialization in the same file:
template <> struct EnumTraits<DescriptiveErrorCode>
{
static constexpr std::array<std::string_view, 18> names = {
// ... existing names ...
"YourNewCheck"
};
};Create include/analysis/YourNewCheck.hpp:
#pragma once
#include <llvm/IR/Module.h>
#include <vector>
namespace ctrace::stack::analysis
{
struct YourNewCheckFinding
{
const llvm::Function* function = nullptr;
const llvm::Instruction* instruction = nullptr;
// ... specific finding fields ...
};
std::vector<YourNewCheckFinding> runYourNewCheck(
const llvm::Module& module,
const llvm::Function& function);
} // namespace ctrace::stack::analysisCreate src/analysis/YourNewCheck.cpp:
#include "analysis/YourNewCheck.hpp"
#include <llvm/IR/Instructions.h>
namespace ctrace::stack::analysis
{
std::vector<YourNewCheckFinding> runYourNewCheck(
const llvm::Module& module,
const llvm::Function& function)
{
std::vector<YourNewCheckFinding> findings;
for (const auto& BB : function)
{
for (const auto& I : BB)
{
// Your analysis logic here
// Iterate over instructions, check conditions, etc.
}
}
return findings;
}
} // namespace ctrace::stack::analysisAdd the new source file to STACK_ANALYZER_SOURCES:
set(STACK_ANALYZER_SOURCES
# ... existing sources ...
src/analysis/YourNewCheck.cpp
)In src/analyzer/AnalysisPipeline.cpp, add a call to your analysis module within the per-function analysis loop:
#include "analysis/YourNewCheck.hpp"
// Inside the analysis loop:
auto yourFindings = analysis::runYourNewCheck(module, function);
// Process findings...In src/analyzer/DiagnosticEmitter.cpp, add a case to convert your findings to Diagnostic structs:
for (const auto& finding : yourFindings)
{
Diagnostic diag;
diag.severity = DiagnosticSeverity::Warning;
diag.errCode = DescriptiveErrorCode::YourNewCheck;
diag.ruleId = "YourNewCheck";
diag.message = "description of the issue";
// Set location from finding.instruction debug info
// via LocationResolver
result.diagnostics.push_back(std::move(diag));
}Create a test file test/your-new-check/basic-case.c:
// Expected diagnostic annotations:
// at line X, column Y
// [ !!Warn ] your diagnostic message here
void test_function(void)
{
// Code that triggers your check
}The test runner (run_test.py) parses comment annotations to validate expected diagnostics. The format is:
// at line <N>, column <N>
// [ !!Warn ] <message prefix>or for info-level:
// [!Info] <message prefix>python3 test/run_test.py --analyzer ./build/stack_usage_analyzer test/your-new-check/If your module has isolated logic worth testing, add tests to test/unit/analyzer_module_unit_tests.cpp:
// Build with: -DBUILD_ANALYZER_UNIT_TESTS=ONIf your check uses external model files (like resource-lifetime or escape analysis), define the model format and add entries to the bundled models in models/.
- New
DescriptiveErrorCodeentry +EnumTraitsupdate - Analysis module header (
include/analysis/) - Analysis module implementation (
src/analysis/) - Source file registered in
CMakeLists.txt - Wired into
AnalysisPipeline.cpp - Diagnostic emission in
DiagnosticEmitter.cpp - Regression test fixture in
test/ - Tests pass with
run_test.py - Code formatted with
clang-format
- Study existing analysis modules for patterns.
DuplicateIfCondition.cppis a good simple example.ResourceLifetimeAnalysis.cppis a complex example with cross-TU support. - Use
IRValueUtils.hppfor common LLVM IR traversal utilities. - Use
LocationResolverto convertllvm::Instruction*to source locations. - The
Reachabilitymodule can filter your findings if they occur in dead code. - Keep analysis modules stateless when possible -- take the module/function as input, return findings as output.