1#include "clang/AST/Attr.h"
2#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
3#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
4#include "clang/StaticAnalyzer/Core/Checker.h"
5#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
6#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
7#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
8#include "llvm/Support/raw_ostream.h"
9
10using namespace clang;
11using namespace ento;
12
13REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
14REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
15
16namespace {
17class UseAfterLifetimeEnd
18 : public Checker<check::PostCall, check::EndFunction, check::DeadSymbols> {
19public:
20 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
21 void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
22 const char *Sep) const override;
23 void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
24 CheckerContext &C) const;
25 void checkReturnedBorrower(SVal Val, ProgramStateRef State,
26 CheckerContext &C) const;
27 void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
28 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
29 const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
30};
31
32} // namespace
33
34static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
35 const MemRegion *Source) {
36 LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
37
38 const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(key: RetVal);
39 LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
40 Set = F.add(Old: Set, V: Source);
41 State = State->set<LifetimeBoundMap>(K: RetVal, E: Set);
42 return State;
43}
44
45void UseAfterLifetimeEnd::checkPostCall(const CallEvent &Call,
46 CheckerContext &C) const {
47 ProgramStateRef State = C.getState();
48
49 const auto *FC = dyn_cast<AnyFunctionCall>(Val: &Call);
50 if (!FC)
51 return;
52
53 const FunctionDecl *FD = FC->getDecl();
54 if (!FD)
55 return;
56
57 SVal RetVal = Call.getReturnValue();
58
59 for (const ParmVarDecl *PVD : FD->parameters()) {
60 if (PVD->hasAttr<LifetimeBoundAttr>()) {
61 unsigned Idx = PVD->getFunctionScopeIndex();
62 SVal Arg = Call.getArgSVal(Index: Idx);
63 if (const MemRegion *ArgValRegion = Arg.getAsRegion())
64 State = bindValues(State, RetVal, Source: ArgValRegion);
65 }
66 }
67
68 if (const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call)) {
69 if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
70 if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
71 State = bindValues(State, RetVal, Source: AttrRegion);
72 }
73 }
74 }
75 C.addTransition(State);
76}
77
78static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
79 CheckerContext &C) {
80 // FIXME: The checker currently handles stack-region sources. Other
81 // region kinds require separate methodology. For example, heap
82 // regions do not go out of scope at the end of a stack frame, so
83 // in order to detect those type of dangling sources the function
84 // needs to be expanded to an event-driven approach as well.
85 if (const auto *StackSpace =
86 Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
87 const StackFrame *SF = StackSpace->getStackFrame();
88 const StackFrame *CurrentSF = C.getStackFrame();
89 if (SF == CurrentSF || !SF->isParentOf(SF: CurrentSF))
90 return true;
91 }
92 return false;
93}
94
95void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State,
96 CheckerContext &C) const {
97 if (auto *SourceSet = State->get<LifetimeBoundMap>(key: Val)) {
98 ExplodedNode *N = nullptr;
99 for (const MemRegion *Region : *SourceSet) {
100 if (hasDanglingSource(Source: Region, State, C)) {
101 if (!N)
102 N = C.generateNonFatalErrorNode();
103 if (!N)
104 return;
105 reportDanglingSource(Region, N, C);
106 }
107 }
108 }
109}
110
111void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
112 CheckerContext &C) const {
113 if (!RS)
114 return;
115
116 ProgramStateRef State = C.getState();
117 auto LBMap = State->get<LifetimeBoundMap>();
118
119 if (LBMap.isEmpty())
120 return;
121
122 const Expr *RetExpr = RS->getRetValue();
123 if (!RetExpr)
124 return;
125
126 RetExpr = RetExpr->IgnoreParens();
127 SVal RetVal = C.getSVal(E: RetExpr);
128 checkReturnedBorrower(Val: RetVal, State, C);
129}
130
131void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
132 ExplodedNode *N,
133 CheckerContext &C) const {
134 auto BR = std::make_unique<PathSensitiveBugReport>(
135 args: BugMsg,
136 args: (llvm::Twine("Returning value bound to '") + Region->getString() +
137 "' that will go out of scope"),
138 args&: N);
139 C.emitReport(R: std::move(BR));
140}
141
142void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
143 CheckerContext &C) const {
144 ProgramStateRef State = C.getState();
145 LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
146
147 for (SVal Val : llvm::make_first_range(c&: LBMap)) {
148 if (const MemRegion *ValRegion = Val.getAsRegion()) {
149 if (!SymReaper.isLiveRegion(region: ValRegion))
150 State = State->remove<LifetimeBoundMap>(K: Val);
151 } else if (SymbolRef ValRef =
152 Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
153 if (!SymReaper.isLive(sym: ValRef))
154 State = State->remove<LifetimeBoundMap>(K: Val);
155 }
156 }
157
158 C.addTransition(State);
159}
160
161void UseAfterLifetimeEnd::printState(raw_ostream &Out, ProgramStateRef State,
162 const char *NL, const char *Sep) const {
163 auto LBMap = State->get<LifetimeBoundMap>();
164
165 if (LBMap.isEmpty())
166 return;
167
168 Out << Sep << "LifetimeBound bindings:" << NL;
169 for (auto &&[OriginSym, SourceSet] : LBMap) {
170 for (const auto *Region : SourceSet)
171 Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
172 }
173}
174
175namespace {
176class DebugUseAfterLifetimeEnd : public Checker<eval::Call> {
177public:
178 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
179 void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
180 CheckerContext &C) const;
181
182 const BugType BugMsg{this, "DebugUseAfterLifetimeEnd",
183 "DebugUseAfterLifetimeEnd"};
184 using FnCheck = void (DebugUseAfterLifetimeEnd::*)(const CallEvent &Call,
185 CheckerContext &C) const;
186
187 const CallDescriptionMap<FnCheck> Callbacks = {
188 {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
189 &DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf},
190 };
191};
192
193} // namespace
194
195bool DebugUseAfterLifetimeEnd::evalCall(const CallEvent &Call,
196 CheckerContext &C) const {
197 const auto *CE = dyn_cast_if_present<CallExpr>(Val: Call.getOriginExpr());
198 if (!CE)
199 return false;
200
201 const FnCheck *Handler = Callbacks.lookup(Call);
202 if (!Handler)
203 return false;
204
205 (this->*(*Handler))(Call, C);
206 return true;
207}
208
209void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
210 const CallEvent &Call, CheckerContext &C) const {
211 ProgramStateRef State = C.getState();
212
213 if (Call.getNumArgs() != 1) {
214 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
215 auto BR = std::make_unique<PathSensitiveBugReport>(
216 args: BugMsg,
217 args: "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
218 args&: N);
219 C.emitReport(R: std::move(BR));
220 }
221 return;
222 }
223
224 SVal ArgSVal = Call.getArgSVal(Index: 0);
225 const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(key: ArgSVal);
226
227 if (!SourceSet)
228 return;
229
230 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
231 llvm::SmallVector<std::string> RegionNames =
232 to_vector(Range: map_range(C: llvm::make_pointee_range(Range: *SourceSet),
233 F: std::mem_fn(pm: &MemRegion::getString)));
234 llvm::sort(C&: RegionNames);
235
236 llvm::SmallString<128> Str;
237 llvm::raw_svector_ostream OS(Str);
238 OS << " Origin " << ArgSVal << " bound to ";
239 llvm::interleaveComma(c: RegionNames, os&: OS);
240 C.emitReport(R: std::make_unique<PathSensitiveBugReport>(args: BugMsg, args: OS.str(), args&: N));
241 }
242}
243
244void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
245 Mgr.registerChecker<UseAfterLifetimeEnd>();
246}
247
248bool ento::shouldRegisterUseAfterLifetimeEnd(const CheckerManager &Mgr) {
249 return true;
250}
251
252void ento::registerDebugUseAfterLifetimeEnd(CheckerManager &Mgr) {
253 Mgr.registerChecker<DebugUseAfterLifetimeEnd>();
254}
255
256bool ento::shouldRegisterDebugUseAfterLifetimeEnd(const CheckerManager &Mgr) {
257 return true;
258}
259