clang 24.0.0git
LifetimeModeling.cpp
Go to the documentation of this file.
1#include "LifetimeModeling.h"
2#include "clang/AST/Attr.h"
9#include "llvm/Support/raw_ostream.h"
10
11using namespace clang;
12using namespace ento;
13
14REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
15REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
16
17REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
18
19namespace {
20
21class LifetimeModeling
22 : public Checker<check::PostCall, check::DeadSymbols,
23 check::PreStmt<DeclStmt>, check::LifetimeEnd> {
24public:
25 void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
26 const char *Sep) const override;
27 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
28 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
29 void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
30 void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const;
31};
32
33} // namespace
34
35static bool isDanglingStackSource(const MemRegion *Source,
37 // FIXME: The checker currently handles stack-region sources. Other
38 // region kinds require separate methodology. For example, heap
39 // regions do not go out of scope at the end of a stack frame, so
40 // in order to detect those type of dangling sources the function
41 // needs to be expanded to an event-driven approach as well.
42 if (const auto *StackSpace =
43 Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
44 const StackFrame *SF = StackSpace->getStackFrame();
45 const StackFrame *CurrentSF = C.getStackFrame();
46 // If any frame on the current stack belongs to a destructor
47 // the warning should be suppressed. When a lifetimebound method
48 // is called from a destructor then its return value is not expected
49 // to outlive the object being destroyed.
50 if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
51 return isa<CXXDestructorDecl>(Frame.getDecl());
52 })) {
53 return false;
54 }
55
56 if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
57 return true;
58 }
59 return false;
60}
61
63 SVal Val, ProgramStateRef State, CheckerContext &C) {
64 std::vector<const MemRegion *> Regions;
65 if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
66 for (const MemRegion *Region : *SourceSet) {
67 if (isDanglingStackSource(Region, State, C))
68 Regions.push_back(Region);
69 }
70 }
71 return Regions;
72}
73
75 const MemRegion *Region) {
76 return State->contains<DeallocatedSourceSet>(Region->getBaseRegion());
77}
78
80 const MemRegion *Source) {
81 LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
82 const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
83
84 LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
85 Set = F.add(Set, Source);
86 State = State->set<LifetimeBoundMap>(RetVal, Set);
87 return State;
88}
89
91 // FIXME: Once the checker supports heap allocation, more region kinds
92 // should be handled to produce the correct descriptive name.
93 if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty())
94 return RegName;
95 return "the region";
96}
97
98void LifetimeModeling::checkPostCall(const CallEvent &Call,
99 CheckerContext &C) const {
100 ProgramStateRef State = C.getState();
101
102 const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
103 if (!FC)
104 return;
105
106 const FunctionDecl *FD = FC->getDecl();
107 if (!FD)
108 return;
109
110 SVal RetVal = Call.getReturnValue();
111
112 for (const ParmVarDecl *PVD : FD->parameters()) {
113 if (PVD->hasAttr<LifetimeBoundAttr>()) {
114 unsigned Idx = PVD->getFunctionScopeIndex();
115 SVal Arg = Call.getArgSVal(Idx);
116 if (const MemRegion *ArgValRegion = Arg.getAsRegion())
117 State = bindSource(State, RetVal, ArgValRegion);
118 }
119 }
120
121 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
123 if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
124 State = bindSource(State, RetVal, ThisRegion);
125 }
126 C.addTransition(State);
127}
128
129void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
130 CheckerContext &C) const {
131 ProgramStateRef State = C.getState();
132
133 SVal SourceVal = State->getLValue(VD, C.getStackFrame());
134 if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
135 State = State->add<DeallocatedSourceSet>(SourceValRegion);
136 C.addTransition(State);
137 }
138}
139
140void LifetimeModeling::checkPreStmt(const DeclStmt *DS,
141 CheckerContext &C) const {
142 ProgramStateRef State = C.getState();
143 for (const auto *I : DS->decls()) {
144 if (const VarDecl *VD = dyn_cast<VarDecl>(I)) {
145 SVal Val = State->getLValue(VD, C.getStackFrame());
146 if (const MemRegion *ValRegion = Val.getAsRegion())
147 State = State->remove<DeallocatedSourceSet>(ValRegion);
148 }
149 }
150 C.addTransition(State);
151}
152
153void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper,
154 CheckerContext &C) const {
155 ProgramStateRef State = C.getState();
156 LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
157 DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
158
159 for (SVal Val : llvm::make_first_range(LBMap)) {
160 if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(R))
161 continue;
162
163 if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true);
164 S && SymReaper.isLive(S))
165 continue;
166
167 State = State->remove<LifetimeBoundMap>(Val);
168 }
169
170 for (const MemRegion *Region : Sources) {
171 if (!SymReaper.isLiveRegion(Region))
172 State = State->remove<DeallocatedSourceSet>(Region);
173 }
174 C.addTransition(State);
175}
176
177void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
178 const char *NL, const char *Sep) const {
179 auto LBMap = State->get<LifetimeBoundMap>();
180
181 if (LBMap.isEmpty())
182 return;
183
184 Out << Sep << "LifetimeBound bindings:" << NL;
185 for (auto &&[OriginSym, SourceSet] : LBMap) {
186 for (const auto *Region : SourceSet)
187 Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
188 }
189}
190
191// FIXME: Eventually move the debug checker to its own source file once
192// it has more functionality.
193namespace {
194class DebugLifetimeModeling : public Checker<eval::Call> {
195public:
196 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
197 void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
198 CheckerContext &C) const;
199 const BugType BugMsg{this, "DebugLifetimeModeling", "DebugLifetimeModeling"};
200 using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call,
201 CheckerContext &C) const;
202
203 const CallDescriptionMap<FnCheck> Callbacks = {
204 {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
205 &DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf},
206 };
207};
208
209} // namespace
210
211bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
212 CheckerContext &C) const {
213 if (!isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
214 return false;
215
216 const FnCheck *Handler = Callbacks.lookup(Call);
217 if (!Handler)
218 return false;
219
220 (this->*(*Handler))(Call, C);
221 return true;
222}
223
224void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
225 const CallEvent &Call, CheckerContext &C) const {
226 ProgramStateRef State = C.getState();
227
228 if (Call.getNumArgs() != 1) {
229 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
230 auto BR = std::make_unique<PathSensitiveBugReport>(
231 BugMsg,
232 "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
233 N);
234 C.emitReport(std::move(BR));
235 }
236 return;
237 }
238
239 SVal ArgSVal = Call.getArgSVal(0);
240 const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
241
242 if (!SourceSet)
243 return;
244
245 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
246 llvm::SmallVector<std::string> RegionNames =
247 to_vector(map_range(llvm::make_pointee_range(*SourceSet),
248 std::mem_fn(&MemRegion::getString)));
249 llvm::sort(RegionNames);
250
251 llvm::SmallString<128> Str;
252 llvm::raw_svector_ostream OS(Str);
253 OS << " Origin '" << ArgSVal << "' bound to ";
254 llvm::interleaveComma(RegionNames, OS,
255 [&](StringRef Name) { OS << "'" << Name << "'"; });
256 C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
257 }
258}
259
260void ento::registerLifetimeModeling(CheckerManager &Mgr) {
261 Mgr.registerChecker<LifetimeModeling>();
262}
263
264bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
265 return true;
266}
267
268void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) {
269 Mgr.registerChecker<DebugLifetimeModeling>();
270}
271
272bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) {
273 return true;
274}
static bool isDanglingStackSource(const MemRegion *Source, ProgramStateRef State, CheckerContext &C)
static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal, const MemRegion *Source)
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
#define REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable set type Name and registers the factory for such sets in the program state,...
#define REGISTER_SET_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable set of type NameTy, suitable for placement into the ProgramState.
decl_range decls()
Definition Stmt.h:1688
Represents a function declaration or definition.
Definition Decl.h:2029
ArrayRef< ParmVarDecl * > parameters() const
Definition Decl.h:2814
Represents a parameter to a function.
Definition Decl.h:1819
It represents a stack frame of the call stack.
bool isParentOf(const StackFrame *SF) const
Represents an abstract call to a function or method along a particular path.
Definition CallEvent.h:152
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:565
MemRegion - The root abstract class for all memory regions.
Definition MemRegion.h:97
std::string getDescriptiveName(bool UseQuotes=true) const
Get descriptive name for memory region.
LLVM_ATTRIBUTE_RETURNS_NONNULL const MemRegion * getBaseRegion() const
std::string getString() const
Get a string representation of a region for debug use.
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
Definition SVals.h:57
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
Definition SVals.cpp:103
const MemRegion * getAsRegion() const
Definition SVals.cpp:119
bool isLiveRegion(const MemRegion *region)
bool isLive(SymbolRef sym)
std::string getRegionName(const MemRegion *Reg)
Returns the descriptive name of the memory region or a placeholder if a descriptive name cannot be co...
bool isDeallocated(ProgramStateRef State, const MemRegion *Region)
Returns true if the underlying MemRegion is deallocated.
std::vector< const MemRegion * > getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State, CheckerContext &C)
Returns the set of lifetime sources bound to Source that are dangling stack regions.
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const SymExpr * SymbolRef
Definition SymExpr.h:133
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
The JSON file list parser is used to communicate input to InstallAPI.