clang 23.0.0git
UseAfterLifetimeEnd.cpp
Go to the documentation of this file.
1#include "clang/AST/Attr.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
35 const MemRegion *Source) {
36 LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
37
38 const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
39 LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
40 Set = F.add(Set, Source);
41 State = State->set<LifetimeBoundMap>(RetVal, 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>(&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(Idx);
63 if (const MemRegion *ArgValRegion = Arg.getAsRegion())
64 State = bindValues(State, RetVal, ArgValRegion);
65 }
66 }
67
68 if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
70 if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
71 State = bindValues(State, RetVal, AttrRegion);
72 }
73 }
74 }
75 C.addTransition(State);
76}
77
78static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
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(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>(Val)) {
98 ExplodedNode *N = nullptr;
99 for (const MemRegion *Region : *SourceSet) {
100 if (hasDanglingSource(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(RetExpr);
128 checkReturnedBorrower(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 BugMsg,
136 (llvm::Twine("Returning value bound to '") + Region->getString() +
137 "' that will go out of scope"),
138 N);
139 C.emitReport(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(LBMap)) {
148 if (const MemRegion *ValRegion = Val.getAsRegion()) {
149 if (!SymReaper.isLiveRegion(ValRegion))
150 State = State->remove<LifetimeBoundMap>(Val);
151 } else if (SymbolRef ValRef =
152 Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
153 if (!SymReaper.isLive(ValRef))
154 State = State->remove<LifetimeBoundMap>(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>(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 BugMsg,
217 "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
218 N);
219 C.emitReport(std::move(BR));
220 }
221 return;
222 }
223
224 SVal ArgSVal = Call.getArgSVal(0);
225 const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
226
227 if (!SourceSet)
228 return;
229
230 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
231 llvm::SmallVector<std::string> RegionNames =
232 to_vector(map_range(llvm::make_pointee_range(*SourceSet),
233 std::mem_fn(&MemRegion::getString)));
234 llvm::sort(RegionNames);
235
236 llvm::SmallString<128> Str;
237 llvm::raw_svector_ostream OS(Str);
238 OS << " Origin " << ArgSVal << " bound to ";
239 llvm::interleaveComma(RegionNames, OS);
240 C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), 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}
#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,...
static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State, CheckerContext &C)
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal, const MemRegion *Source)
Expr * IgnoreParens() LLVM_READONLY
Skip past any parentheses which might surround this expression until reaching a fixed point.
Definition Expr.cpp:3095
ArrayRef< ParmVarDecl * > parameters() const
Definition Decl.h:2801
Expr * getRetValue()
Definition Stmt.h:3197
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 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)
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.