1#include "LifetimeModeling.h"
2#include "clang/AST/Attr.h"
3#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
4#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
5#include "clang/StaticAnalyzer/Core/Checker.h"
6#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
7#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
8#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.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 *)
18REGISTER_SET_WITH_PROGRAMSTATE(ReportedDeadRegions, const MemRegion *)
19
20namespace {
21
22class LifetimeModeling
23 : public Checker<check::PostCall, check::DeadSymbols,
24 check::PreStmt<DeclStmt>, check::LifetimeEnd> {
25public:
26 void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
27 const char *Sep) const override;
28 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
29 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
30 void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
31 void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const;
32};
33
34} // namespace
35
36static bool isDanglingStackSource(const MemRegion *Source,
37 ProgramStateRef State, CheckerContext &C) {
38 // FIXME: The checker currently handles stack-region sources. Other
39 // region kinds require separate methodology. For example, heap
40 // regions do not go out of scope at the end of a stack frame, so
41 // in order to detect those type of dangling sources the function
42 // needs to be expanded to an event-driven approach as well.
43 if (const auto *StackSpace =
44 Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
45 const StackFrame *SF = StackSpace->getStackFrame();
46 const StackFrame *CurrentSF = C.getStackFrame();
47 // If any frame on the current stack belongs to a destructor
48 // the warning should be suppressed. When a lifetimebound method
49 // is called from a destructor then its return value is not expected
50 // to outlive the object being destroyed.
51 if (llvm::any_of(Range: C.stackframes(), P: [&](const StackFrame &Frame) {
52 return isa<CXXDestructorDecl>(Val: Frame.getDecl());
53 })) {
54 return false;
55 }
56 // Only a source whose frame is still live on the current stack can
57 // dangle. If that frame is not on the stack then the source outlives
58 // the returned value. The source is still alive when the returned value
59 // is used, so it does not dangle.
60 if (is_contained(Range: make_pointer_range(Range: C.stackframes()), Element: SF)) {
61 if (SF == CurrentSF || !SF->isParentOf(SF: CurrentSF))
62 return true;
63 }
64 }
65 return false;
66}
67
68std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
69 SVal Val, ProgramStateRef State, CheckerContext &C) {
70 std::vector<const MemRegion *> Regions;
71 if (auto *SourceSet = State->get<LifetimeBoundMap>(key: Val)) {
72 for (const MemRegion *Region : *SourceSet) {
73 if (isDanglingStackSource(Source: Region, State, C))
74 Regions.push_back(x: Region);
75 }
76 }
77 return Regions;
78}
79
80bool lifetime_modeling::isBoundToLifetimeSource(ProgramStateRef State,
81 SVal Val) {
82 return State->get<LifetimeBoundMap>(key: Val) != nullptr;
83}
84
85bool lifetime_modeling::isDeallocated(ProgramStateRef State,
86 const MemRegion *Region) {
87 return State->contains<DeallocatedSourceSet>(key: Region->getBaseRegion());
88}
89
90ProgramStateRef lifetime_modeling::markAsReported(ProgramStateRef State,
91 const MemRegion *Region) {
92 ProgramStateRef NewState =
93 State->add<ReportedDeadRegions>(K: Region->getBaseRegion());
94
95 return (NewState != State) ? NewState : nullptr;
96}
97
98static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
99 const MemRegion *Source) {
100 LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
101 const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(key: RetVal);
102
103 LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
104 Set = F.add(Old: Set, V: Source);
105 State = State->set<LifetimeBoundMap>(K: RetVal, E: Set);
106 return State;
107}
108
109std::string lifetime_modeling::getRegionName(const MemRegion *Reg) {
110 // FIXME: Once the checker supports heap allocation, more region kinds
111 // should be handled to produce the correct descriptive name.
112 if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty())
113 return RegName;
114 return "the region";
115}
116
117void LifetimeModeling::checkPostCall(const CallEvent &Call,
118 CheckerContext &C) const {
119 ProgramStateRef State = C.getState();
120
121 const auto *FC = dyn_cast<AnyFunctionCall>(Val: &Call);
122 if (!FC)
123 return;
124
125 const FunctionDecl *FD = FC->getDecl();
126 if (!FD)
127 return;
128
129 SVal RetVal = Call.getReturnValue();
130
131 for (const ParmVarDecl *PVD : FD->parameters()) {
132 if (PVD->hasAttr<LifetimeBoundAttr>()) {
133 unsigned Idx = PVD->getFunctionScopeIndex();
134 SVal Arg = Call.getArgSVal(Index: Idx);
135 if (const MemRegion *ArgValRegion = Arg.getAsRegion())
136 State = bindSource(State, RetVal, Source: ArgValRegion);
137 }
138 }
139
140 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
141 if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
142 if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
143 State = bindSource(State, RetVal, Source: ThisRegion);
144 }
145 C.addTransition(State);
146}
147
148void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
149 CheckerContext &C) const {
150 ProgramStateRef State = C.getState();
151
152 SVal SourceVal = State->getLValue(VD, SF: C.getStackFrame());
153 if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
154 State = State->add<DeallocatedSourceSet>(K: SourceValRegion);
155 C.addTransition(State);
156 }
157}
158
159void LifetimeModeling::checkPreStmt(const DeclStmt *DS,
160 CheckerContext &C) const {
161 ProgramStateRef State = C.getState();
162 for (const auto *I : DS->decls()) {
163 if (const VarDecl *VD = dyn_cast<VarDecl>(Val: I)) {
164 SVal Val = State->getLValue(VD, SF: C.getStackFrame());
165 if (const MemRegion *ValRegion = Val.getAsRegion())
166 State = State->remove<DeallocatedSourceSet>(K: ValRegion);
167 }
168 }
169 C.addTransition(State);
170}
171
172void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper,
173 CheckerContext &C) const {
174 ProgramStateRef State = C.getState();
175 LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
176 DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
177 ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>();
178
179 for (SVal Val : llvm::make_first_range(c&: LBMap)) {
180 if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(region: R))
181 continue;
182
183 if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true);
184 S && SymReaper.isLive(sym: S))
185 continue;
186
187 State = State->remove<LifetimeBoundMap>(K: Val);
188 }
189
190 for (const MemRegion *Region : Sources) {
191 if (!SymReaper.isLiveRegion(region: Region))
192 State = State->remove<DeallocatedSourceSet>(K: Region);
193 }
194
195 for (const MemRegion *Region : Reported) {
196 if (!SymReaper.isLiveRegion(region: Region))
197 State = State->remove<ReportedDeadRegions>(K: Region);
198 }
199 C.addTransition(State);
200}
201
202void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
203 const char *NL, const char *Sep) const {
204 auto LBMap = State->get<LifetimeBoundMap>();
205 ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>();
206
207 if (!LBMap.isEmpty()) {
208 Out << Sep << "LifetimeBound bindings:" << NL;
209 for (auto &&[OriginSym, SourceSet] : LBMap) {
210 for (const auto *Region : SourceSet)
211 Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
212 }
213 }
214
215 if (!Reported.isEmpty()) {
216 Out << Sep << "Reported regions: " << NL;
217 for (const auto *Region : Reported) {
218 Out << " " << Region << NL;
219 }
220 }
221}
222
223// FIXME: Eventually move the debug checker to its own source file once
224// it has more functionality.
225namespace {
226class DebugLifetimeModeling : public Checker<eval::Call> {
227public:
228 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
229 void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
230 CheckerContext &C) const;
231 const BugType BugMsg{this, "DebugLifetimeModeling", "DebugLifetimeModeling"};
232 using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call,
233 CheckerContext &C) const;
234
235 const CallDescriptionMap<FnCheck> Callbacks = {
236 {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
237 &DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf},
238 };
239};
240
241} // namespace
242
243bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
244 CheckerContext &C) const {
245 if (!isa_and_nonnull<CallExpr>(Val: Call.getOriginExpr()))
246 return false;
247
248 const FnCheck *Handler = Callbacks.lookup(Call);
249 if (!Handler)
250 return false;
251
252 (this->*(*Handler))(Call, C);
253 return true;
254}
255
256void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
257 const CallEvent &Call, CheckerContext &C) const {
258 ProgramStateRef State = C.getState();
259
260 if (Call.getNumArgs() != 1) {
261 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
262 auto BR = std::make_unique<PathSensitiveBugReport>(
263 args: BugMsg,
264 args: "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
265 args&: N);
266 C.emitReport(R: std::move(BR));
267 }
268 return;
269 }
270
271 SVal ArgSVal = Call.getArgSVal(Index: 0);
272 const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(key: ArgSVal);
273
274 if (!SourceSet)
275 return;
276
277 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
278 llvm::SmallVector<std::string> RegionNames =
279 to_vector(Range: map_range(C: llvm::make_pointee_range(Range: *SourceSet),
280 F: std::mem_fn(pm: &MemRegion::getString)));
281 llvm::sort(C&: RegionNames);
282
283 llvm::SmallString<128> Str;
284 llvm::raw_svector_ostream OS(Str);
285 OS << " Origin '" << ArgSVal << "' bound to ";
286 llvm::interleaveComma(c: RegionNames, os&: OS,
287 each_fn: [&](StringRef Name) { OS << "'" << Name << "'"; });
288 C.emitReport(R: std::make_unique<PathSensitiveBugReport>(args: BugMsg, args: OS.str(), args&: N));
289 }
290}
291
292void ento::registerLifetimeModeling(CheckerManager &Mgr) {
293 Mgr.registerChecker<LifetimeModeling>();
294}
295
296bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
297 return true;
298}
299
300void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) {
301 Mgr.registerChecker<DebugLifetimeModeling>();
302}
303
304bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) {
305 return true;
306}
307