Интерпретация вопроса
В частности, вы хотите подсчитать вызовы inc
и dec
, примененные к каждой переменной, и сообщить, когда они не сбалансированы для некоторого пути в функции.
Как правило, вы хотите знать, как связать абстрактное значение, здесь число, с переменной программы, и иметь возможность обновлять и запрашивать это значение на каждом пути выполнения.
Ответ высокого уровня
Принимая во внимание, что средство проверки учебника SimpleStreamChecker.cpp
связывает абстрактное значение со значением, хранящимся в переменной, здесь мы хотим связать абстрактное значение с самой переменной. Это то, что IteratorChecker.cpp
делает при отслеживании контейнеров, поэтому я основывал свое решение на нем.
В абстрактном состоянии статического анализатора каждая переменная представлена объектом MemRegion
. Итак, первый шаг — создать карту, где MemRegion
— это ключ:
REGISTER_MAP_WITH_PROGRAMSTATE(TrackVarMap, MemRegion const *, int)
Затем, когда у нас есть SVal
, который соответствует указателю на переменную, мы можем используйте SVal::getAsRegion
, чтобы получить соответствующий MemRegion
. Например, для CallEvent
, call
, с первым аргументом, который является указателем, мы можем:
if (MemRegion const *region = call.getArgSVal(0).getAsRegion()) {
чтобы получить region
, на которое указывает указатель.
Затем мы можем получить доступ к нашей карте, используя этот region
в качестве ключа:
state = state->set<TrackVarMap>(region, newValue);
Наконец, в checkDeadSymbols
мы используем SymbolReaper::isLiveRegion
, чтобы определить, когда регион (переменная) выходит за рамки:
const TrackVarMapTy &Map = state->get<TrackVarMap>();
for (auto const &I : Map) {
MemRegion const *region = I.first;
int delta = I.second;
if (SymReaper.isLiveRegion(region) || (delta==0))
continue; // Not dead, or unchanged; skip.
Полный пример
Чтобы продемонстрировать, вот полная проверка, которая сообщает о несбалансированном использовании inc
и dec
:
// TrackVarChecker.cpp
// https://stackoverflow.com/questions/23448540/how-to-keep-track-of-a-variable-with-clangs-static-analyzer
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
using namespace clang;
using namespace ento;
namespace {
class TrackVarChecker
: public Checker< check::PostCall,
check::DeadSymbols >
{
mutable IdentifierInfo *II_inc, *II_dec;
mutable std::unique_ptr<BuiltinBug> BT_modified;
public:
TrackVarChecker() : II_inc(nullptr), II_dec(nullptr) {}
void checkPostCall(CallEvent const &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
} // end anonymous namespace
// Map from memory region corresponding to a variable (that is, the
// variable itself, not its current value) to the difference between its
// current and original value.
REGISTER_MAP_WITH_PROGRAMSTATE(TrackVarMap, MemRegion const *, int)
void TrackVarChecker::checkPostCall(CallEvent const &call, CheckerContext &C) const
{
const FunctionDecl *FD = dyn_cast<FunctionDecl>(call.getDecl());
if (!FD || FD->getKind() != Decl::Function) {
return;
}
ASTContext &Ctx = C.getASTContext();
if (!II_inc) {
II_inc = &Ctx.Idents.get("inc");
}
if (!II_dec) {
II_dec = &Ctx.Idents.get("dec");
}
if (FD->getIdentifier() == II_inc || FD->getIdentifier() == II_dec) {
// We expect the argument to be a pointer. Get the memory region
// that the pointer points at.
if (MemRegion const *region = call.getArgSVal(0).getAsRegion()) {
// Increment the associated value, creating it first if needed.
ProgramStateRef state = C.getState();
int delta = (FD->getIdentifier() == II_inc)? +1 : -1;
int const *curp = state->get<TrackVarMap>(region);
int newValue = (curp? *curp : 0) + delta;
state = state->set<TrackVarMap>(region, newValue);
C.addTransition(state);
}
}
}
void TrackVarChecker::checkDeadSymbols(
SymbolReaper &SymReaper, CheckerContext &C) const
{
ProgramStateRef state = C.getState();
const TrackVarMapTy &Map = state->get<TrackVarMap>();
for (auto const &I : Map) {
// Check for a memory region (variable) going out of scope that has
// a non-zero delta.
MemRegion const *region = I.first;
int delta = I.second;
if (SymReaper.isLiveRegion(region) || (delta==0)) {
continue; // Not dead, or unchanged; skip.
}
//llvm::errs() << region << " dead with delta " << delta << "\n";
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT_modified) {
BT_modified.reset(
new BuiltinBug(this, "Delta not zero",
"Variable changed from its original value."));
}
C.emitReport(llvm::make_unique<BugReport>(
*BT_modified, BT_modified->getDescription(), N));
}
}
}
void ento::registerTrackVarChecker(CheckerManager &mgr) {
mgr.registerChecker<TrackVarChecker>();
}
bool ento::shouldRegisterTrackVarChecker(const LangOptions &LO) {
return true;
}
Чтобы подключить это к остальной части Clang, добавьте записи в:
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
и
clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
Пример ввода для проверки:
// trackvar.c
// Test for TrackVarChecker.
// The behavior of these functions is hardcoded in the checker.
void inc(int *num);
void dec(int *num);
void call_inc(int var) {
inc(&var);
} // reported
void call_inc_dec(int var) {
inc(&var);
dec(&var);
} // NOT reported
void if_inc(int var) {
if (var > 2) {
inc(&var);
}
} // reported
void indirect_inc(int val) {
int *p = &val;
inc(p);
} // reported
Пример запуска:
$ gcc -E -o trackvar.i trackvar.c
$ ~/bld/llvm-project/build/bin/clang -cc1 -analyze -analyzer-checker=alpha.core.TrackVar trackvar.i
trackvar.c:10:1: warning: Variable changed from its original value
}
^
trackvar.c:21:1: warning: Variable changed from its original value
}
^
trackvar.c:26:1: warning: Variable changed from its original value
}
^
3 warnings generated.
person
Scott McPeak
schedule
12.09.2019