Clang静态分析器Checker:引用类型get/set函数断言崩溃排查
Clang静态分析器Checker处理引用返回函数时的断言崩溃问题
问题背景
需要实现一个Clang静态分析器Checker,处理一对函数:
void set(const int& value); const int& get();
实际逻辑是set将传入引用指向的值保存到内部int变量,get返回该变量的引用。原Checker实现代码如下:
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; namespace { class checkerTest : public Checker<eval::Call> { bool handleSet(CheckerContext &C, const CallEvent &Call) const; bool handleGet(CheckerContext &C, const CallEvent &Call) const; public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; using FnHandler = bool (checkerTest::*)(CheckerContext &, const CallEvent &Call) const; CallDescriptionMap<FnHandler> Functions = { {{{"set"}, 1}, &checkerTest::handleSet}, {{{"get"}, 0}, &checkerTest::handleGet}, }; }; } // namespace SVal g_value; bool checkerTest::handleSet(CheckerContext &C, const CallEvent &Call) const { SVal location = Call.getArgSVal(0); QualType LoadTy = Call.getArgExpr(0)->getType(); ProgramStateRef State = C.getState(); SVal Value = State->getSVal(location.castAs<Loc>(), LoadTy); C.addTransition(State); g_value = Value; return true; } bool checkerTest::handleGet(CheckerContext &C, const CallEvent &Call) const { ProgramStateRef State = C.getState(); State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), g_value); C.addTransition(State); return true; } bool checkerTest::evalCall(const CallEvent &Call, CheckerContext &C) const { const FnHandler *Handler = Functions.lookup(Call); if (Handler) { return (this->**Handler)(C, Call); } return false; } void ento::registercheckerTest(CheckerManager &mgr) { mgr.registerChecker<checkerTest>(); } bool ento::shouldRegistercheckerTest(const CheckerManager &mgr) { if (mgr.getLangOpts().CPlusPlus) return true; return false; }
测试以下代码时触发断言崩溃:
void set(const int& value); const int& get(); int main() { set(0); int res = get(); return res; }
崩溃信息:
Assertion `!isa<NonLoc>(location) && "location cannot be a NonLoc."' failed.
问题原因
- 返回类型不匹配:
get()的返回类型是const int&,静态分析器期望该函数的返回值是内存位置(Loc类型的SVal),但原代码中保存的g_value是从参数加载的数值(NonLoc类型)。当后续代码尝试对get()的返回值执行加载操作时,分析器发现传入的是NonLoc而非Loc,触发断言。 - 全局变量存储状态错误:使用全局变量
g_value保存状态违反了静态分析的路径敏感性,不同分析路径的状态会互相覆盖,导致分析结果错误。正确的做法是将状态存储在ProgramState中。
修正方案
核心思路
- 模拟
set和get背后的内部变量:创建一个符号化的内存位置代表这个内部变量 - 在
handleSet中,将传入参数的值存储到这个内部变量的位置,并将该位置保存到ProgramState - 在
handleGet中,返回这个内部变量的位置,作为函数的返回值
修正后的完整代码
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; namespace { class checkerTest : public Checker<eval::Call> { bool handleSet(CheckerContext &C, const CallEvent &Call) const; bool handleGet(CheckerContext &C, const CallEvent &Call) const; public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; using FnHandler = bool (checkerTest::*)(CheckerContext &, const CallEvent &Call) const; CallDescriptionMap<FnHandler> Functions = { {{{"set"}, 1}, &checkerTest::handleSet}, {{{"get"}, 0}, &checkerTest::handleGet}, }; }; // 注册用于存储内部变量位置的ProgramState trait REGISTER_TRAIT_WITH_PROGRAMSTATE(InternalVarLoc, Loc) } // namespace bool checkerTest::handleSet(CheckerContext &C, const CallEvent &Call) const { ProgramStateRef State = C.getState(); // 获取传入参数的位置和值 SVal ArgLoc = Call.getArgSVal(0).castAs<Loc>(); QualType ArgTy = Call.getArgExpr(0)->getType().getNonReferenceType(); SVal ArgValue = State->getSVal(ArgLoc, ArgTy); // 创建一个符号化的位置代表内部变量 const LocationContext *LCtx = C.getLocationContext(); QualType IntTy = C.getASTContext().IntTy; SymbolRef InternalVarSym = C.getSymbolManager().getFreshSymbol(IntTy, LCtx, Call.getOriginExpr()); Loc InternalVarLoc = C.getSValBuilder().makeSymbolVal(InternalVarSym, IntTy).castAs<Loc>(); // 将参数的值存储到内部变量的位置 State = State->setSVal(InternalVarLoc, ArgValue); // 将内部变量的位置保存到ProgramState State = State->set<InternalVarLoc>(InternalVarLoc); C.addTransition(State); return true; } bool checkerTest::handleGet(CheckerContext &C, const CallEvent &Call) const { ProgramStateRef State = C.getState(); // 从ProgramState中取出内部变量的位置 Loc InternalVarLoc = State->get<InternalVarLoc>(); if (!InternalVarLoc.isValid()) { // 如果还没调用过set,这里可以根据需求处理,比如返回未知值 return false; } // 将内部变量的位置绑定为get()函数的返回值(因为返回的是引用) State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), InternalVarLoc); C.addTransition(State); return true; } bool checkerTest::evalCall(const CallEvent &Call, CheckerContext &C) const { const FnHandler *Handler = Functions.lookup(Call); if (Handler) { return (this->**Handler)(C, Call); } return false; } void ento::registercheckerTest(CheckerManager &mgr) { mgr.registerChecker<checkerTest>(); } bool ento::shouldRegistercheckerTest(const CheckerManager &mgr) { return mgr.getLangOpts().CPlusPlus; }
关键修改点说明
REGISTER_TRAIT_WITH_PROGRAMSTATE:自定义ProgramState的扩展字段,用于存储内部变量的位置,替代原有的全局变量,保证路径敏感的状态管理。handleSet逻辑:创建新的符号化位置代表内部变量,将传入参数的值写入该位置,并将位置存入ProgramState。handleGet逻辑:从ProgramState取出内部变量的位置,将其绑定为get()函数的返回值(符合引用类型的要求,返回内存位置而非值)。
内容的提问来源于stack exchange,提问作者nevilad
相关产品推荐
相关产品推荐

