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 *)
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,
36 ProgramStateRef State, CheckerContext &C) {
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 for (const StackFrame *DtorSF = CurrentSF; DtorSF;
51 DtorSF = DtorSF->getParent()) {
52 if (isa<CXXDestructorDecl>(Val: DtorSF->getDecl()))
53 return false;
54 }
55
56 if (SF == CurrentSF || !SF->isParentOf(SF: CurrentSF))
57 return true;
58 }
59 return false;
60}
61
62std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
63 SVal Val, ProgramStateRef State, CheckerContext &C) {
64 std::vector<const MemRegion *> Regions;
65 if (auto *SourceSet = State->get<LifetimeBoundMap>(key: Val)) {
66 for (const MemRegion *Region : *SourceSet) {
67 if (isDanglingStackSource(Source: Region, State, C))
68 Regions.push_back(x: Region);
69 }
70 }
71 return Regions;
72}
73
74bool lifetime_modeling::isDeallocated(ProgramStateRef State,
75 const MemRegion *Region) {
76 return State->contains<DeallocatedSourceSet>(key: Region);
77}
78
79static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
80 const MemRegion *Source) {
81 LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
82 const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(key: RetVal);
83
84 LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
85 Set = F.add(Old: Set, V: Source);
86 State = State->set<LifetimeBoundMap>(K: RetVal, E: Set);
87 return State;
88}
89
90void LifetimeModeling::checkPostCall(const CallEvent &Call,
91 CheckerContext &C) const {
92 ProgramStateRef State = C.getState();
93
94 const auto *FC = dyn_cast<AnyFunctionCall>(Val: &Call);
95 if (!FC)
96 return;
97
98 const FunctionDecl *FD = FC->getDecl();
99 if (!FD)
100 return;
101
102 SVal RetVal = Call.getReturnValue();
103
104 for (const ParmVarDecl *PVD : FD->parameters()) {
105 if (PVD->hasAttr<LifetimeBoundAttr>()) {
106 unsigned Idx = PVD->getFunctionScopeIndex();
107 SVal Arg = Call.getArgSVal(Index: Idx);
108 if (const MemRegion *ArgValRegion = Arg.getAsRegion())
109 State = bindSource(State, RetVal, Source: ArgValRegion);
110 }
111 }
112
113 const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call);
114 if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
115 if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
116 State = bindSource(State, RetVal, Source: ThisRegion);
117 }
118 C.addTransition(State);
119}
120
121void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
122 CheckerContext &C) const {
123 ProgramStateRef State = C.getState();
124
125 SVal SourceVal = State->getLValue(VD, SF: C.getStackFrame());
126 if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
127 State = State->add<DeallocatedSourceSet>(K: SourceValRegion);
128 C.addTransition(State);
129 }
130}
131
132void LifetimeModeling::checkPreStmt(const DeclStmt *DS,
133 CheckerContext &C) const {
134 ProgramStateRef State = C.getState();
135 for (const auto *I : DS->decls()) {
136 if (const VarDecl *VD = dyn_cast<VarDecl>(Val: I)) {
137 SVal Val = State->getLValue(VD, SF: C.getStackFrame());
138 if (const MemRegion *ValRegion = Val.getAsRegion())
139 State = State->remove<DeallocatedSourceSet>(K: ValRegion);
140 }
141 }
142 C.addTransition(State);
143}
144
145void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper,
146 CheckerContext &C) const {
147 ProgramStateRef State = C.getState();
148 LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
149 DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
150
151 for (SVal Val : llvm::make_first_range(c&: LBMap)) {
152 if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(region: R))
153 continue;
154
155 if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true);
156 S && SymReaper.isLive(sym: S))
157 continue;
158
159 State = State->remove<LifetimeBoundMap>(K: Val);
160 }
161
162 for (const MemRegion *Region : Sources) {
163 if (!SymReaper.isLiveRegion(region: Region))
164 State = State->remove<DeallocatedSourceSet>(K: Region);
165 }
166 C.addTransition(State);
167}
168
169void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
170 const char *NL, const char *Sep) const {
171 auto LBMap = State->get<LifetimeBoundMap>();
172
173 if (LBMap.isEmpty())
174 return;
175
176 Out << Sep << "LifetimeBound bindings:" << NL;
177 for (auto &&[OriginSym, SourceSet] : LBMap) {
178 for (const auto *Region : SourceSet)
179 Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
180 }
181}
182
183// FIXME: Eventually move the debug checker to its own source file once
184// it has more functionality.
185namespace {
186class DebugLifetimeModeling : public Checker<eval::Call> {
187public:
188 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
189 void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
190 CheckerContext &C) const;
191 const BugType BugMsg{this, "DebugLifetimeModeling", "DebugLifetimeModeling"};
192 using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call,
193 CheckerContext &C) const;
194
195 const CallDescriptionMap<FnCheck> Callbacks = {
196 {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
197 &DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf},
198 };
199};
200
201} // namespace
202
203bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
204 CheckerContext &C) const {
205 if (!isa_and_nonnull<CallExpr>(Val: Call.getOriginExpr()))
206 return false;
207
208 const FnCheck *Handler = Callbacks.lookup(Call);
209 if (!Handler)
210 return false;
211
212 (this->*(*Handler))(Call, C);
213 return true;
214}
215
216void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
217 const CallEvent &Call, CheckerContext &C) const {
218 ProgramStateRef State = C.getState();
219
220 if (Call.getNumArgs() != 1) {
221 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
222 auto BR = std::make_unique<PathSensitiveBugReport>(
223 args: BugMsg,
224 args: "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
225 args&: N);
226 C.emitReport(R: std::move(BR));
227 }
228 return;
229 }
230
231 SVal ArgSVal = Call.getArgSVal(Index: 0);
232 const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(key: ArgSVal);
233
234 if (!SourceSet)
235 return;
236
237 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
238 llvm::SmallVector<std::string> RegionNames =
239 to_vector(Range: map_range(C: llvm::make_pointee_range(Range: *SourceSet),
240 F: std::mem_fn(pm: &MemRegion::getString)));
241 llvm::sort(C&: RegionNames);
242
243 llvm::SmallString<128> Str;
244 llvm::raw_svector_ostream OS(Str);
245 OS << " Origin " << ArgSVal << " bound to ";
246 llvm::interleaveComma(c: RegionNames, os&: OS);
247 C.emitReport(R: std::make_unique<PathSensitiveBugReport>(args: BugMsg, args: OS.str(), args&: N));
248 }
249}
250
251void ento::registerLifetimeModeling(CheckerManager &Mgr) {
252 Mgr.registerChecker<LifetimeModeling>();
253}
254
255bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
256 return true;
257}
258
259void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) {
260 Mgr.registerChecker<DebugLifetimeModeling>();
261}
262
263bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) {
264 return true;
265}
266