I'm writing a clang static analyzer checker for this class:
class A { int member_; public: void set(const int& value); const int& get(); }; Real set implementation saves passed value to an internal variable of type int, get returns the reference to this variable.
This is my implementation:
#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 checkerObjTest : public Checker<eval::Call> { bool handleSet(CheckerContext &C, const CallEvent &Call) const; public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; using FnHandler = bool (checkerObjTest::*)(CheckerContext &, const CallEvent &Call) const; CallDescriptionMap<FnHandler> Functions = { {{{"set"}, 1}, &checkerObjTest::handleSet}, }; }; } // namespace bool checkerObjTest::handleSet(CheckerContext &C, const CallEvent &Call) const { const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) return false; const CXXInstanceCall *InstCall = dyn_cast<CXXInstanceCall>(&Call); if (!InstCall) return false; // Conversion to CXXThisExpr returns null in my example, conversion to // Expr returns a valid pointer. //const CXXThisExpr *TE = dyn_cast_or_null<CXXThisExpr>(InstCall->getCXXThisExpr()); const Expr *TE = dyn_cast_or_null<Expr>(InstCall->getCXXThisExpr()); if (!TE) return false; unsigned Count = C.blockCount(); SValBuilder &svalBuilder = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); // Create memory region for object data // Both of these calls trigger a Loc::isLocType(type) assertion. DefinedSVal innerDataVal = svalBuilder.getConjuredHeapSymbolVal(TE, LCtx, Count).castAs<DefinedSVal>(); //DefinedSVal innerDataVal = svalBuilder.getConjuredHeapSymbolVal(CE, LCtx, Count).castAs<DefinedSVal>(); return true; } bool checkerObjTest::evalCall(const CallEvent &Call, CheckerContext &C) const { const FnHandler *Handler = Functions.lookup(Call); if (Handler) { return (this->**Handler)(C, Call); } return false; } void ento::registercheckerObjTest(CheckerManager &mgr) { mgr.registerChecker<checkerObjTest>(); } bool ento::shouldRegistercheckerObjTest(const CheckerManager &mgr) { if (mgr.getLangOpts().CPlusPlus) return true; return false; } On this simple test
class A { int member_; public: void set(const int& value); const int& get(); }; int main() { A a; int data = 0; a.set(data); int res = a.get(); return res; } clang crashes due to an assert in SValBuilder::getConjuredHeapSymbolVal:
Assertion `Loc::isLocType(type)' failed. Looks like the engine expects the first argument to getConjuredHeapSymbolVal to be a location. I tried expressions for call and this, both asserting. I expected that at least this should represent a location but it is not. What location should I use in this example or is it possible to create a HeapSymbol without passing a location?
I tried to model object members as a heap region with elements at given offsets, like malloc and array accesses to allocated memory are working. Maybe this method is wrong for modeling internal states of objects and there is a right one?
I used clang-16.