1 //=== InnerPointerChecker.cpp -------------------------------------*- C++ -*--//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a check that marks a raw pointer to a C++ container's
10 // inner buffer released when the object is destroyed. This information can
11 // be used by MallocChecker to detect use-after-free problems.
13 //===----------------------------------------------------------------------===//
15 #include "AllocationState.h"
16 #include "InterCheckerAPI.h"
17 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19 #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
20 #include "clang/StaticAnalyzer/Core/Checker.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
25 using namespace clang
;
28 // Associate container objects with a set of raw pointer symbols.
29 REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(PtrSet
, SymbolRef
)
30 REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap
, const MemRegion
*, PtrSet
)
35 class InnerPointerChecker
36 : public Checker
<check::DeadSymbols
, check::PostCall
> {
38 CallDescription AppendFn
, AssignFn
, AddressofFn
, AddressofFn_
, ClearFn
,
39 CStrFn
, DataFn
, DataMemberFn
, EraseFn
, InsertFn
, PopBackFn
, PushBackFn
,
40 ReplaceFn
, ReserveFn
, ResizeFn
, ShrinkToFitFn
, SwapFn
;
43 class InnerPointerBRVisitor
: public BugReporterVisitor
{
47 InnerPointerBRVisitor(SymbolRef Sym
) : PtrToBuf(Sym
) {}
49 static void *getTag() {
54 void Profile(llvm::FoldingSetNodeID
&ID
) const override
{
55 ID
.AddPointer(getTag());
58 PathDiagnosticPieceRef
VisitNode(const ExplodedNode
*N
,
59 BugReporterContext
&BRC
,
60 PathSensitiveBugReport
&BR
) override
;
62 // FIXME: Scan the map once in the visitor's constructor and do a direct
64 bool isSymbolTracked(ProgramStateRef State
, SymbolRef Sym
) {
65 RawPtrMapTy Map
= State
->get
<RawPtrMap
>();
66 for (const auto &Entry
: Map
) {
67 if (Entry
.second
.contains(Sym
))
75 : AppendFn({"std", "basic_string", "append"}),
76 AssignFn({"std", "basic_string", "assign"}),
77 AddressofFn({"std", "addressof"}), AddressofFn_({"std", "__addressof"}),
78 ClearFn({"std", "basic_string", "clear"}),
79 CStrFn({"std", "basic_string", "c_str"}), DataFn({"std", "data"}, 1),
80 DataMemberFn({"std", "basic_string", "data"}),
81 EraseFn({"std", "basic_string", "erase"}),
82 InsertFn({"std", "basic_string", "insert"}),
83 PopBackFn({"std", "basic_string", "pop_back"}),
84 PushBackFn({"std", "basic_string", "push_back"}),
85 ReplaceFn({"std", "basic_string", "replace"}),
86 ReserveFn({"std", "basic_string", "reserve"}),
87 ResizeFn({"std", "basic_string", "resize"}),
88 ShrinkToFitFn({"std", "basic_string", "shrink_to_fit"}),
89 SwapFn({"std", "basic_string", "swap"}) {}
91 /// Check whether the called member function potentially invalidates
92 /// pointers referring to the container object's inner buffer.
93 bool isInvalidatingMemberFunction(const CallEvent
&Call
) const;
95 /// Check whether the called function returns a raw inner pointer.
96 bool isInnerPointerAccessFunction(const CallEvent
&Call
) const;
98 /// Mark pointer symbols associated with the given memory region released
99 /// in the program state.
100 void markPtrSymbolsReleased(const CallEvent
&Call
, ProgramStateRef State
,
101 const MemRegion
*ObjRegion
,
102 CheckerContext
&C
) const;
104 /// Standard library functions that take a non-const `basic_string` argument by
105 /// reference may invalidate its inner pointers. Check for these cases and
106 /// mark the pointers released.
107 void checkFunctionArguments(const CallEvent
&Call
, ProgramStateRef State
,
108 CheckerContext
&C
) const;
110 /// Record the connection between raw pointers referring to a container
111 /// object's inner buffer and the object's memory region in the program state.
112 /// Mark potentially invalidated pointers released.
113 void checkPostCall(const CallEvent
&Call
, CheckerContext
&C
) const;
115 /// Clean up the program state map.
116 void checkDeadSymbols(SymbolReaper
&SymReaper
, CheckerContext
&C
) const;
119 } // end anonymous namespace
121 bool InnerPointerChecker::isInvalidatingMemberFunction(
122 const CallEvent
&Call
) const {
123 if (const auto *MemOpCall
= dyn_cast
<CXXMemberOperatorCall
>(&Call
)) {
124 OverloadedOperatorKind Opc
= MemOpCall
->getOriginExpr()->getOperator();
125 if (Opc
== OO_Equal
|| Opc
== OO_PlusEqual
)
129 return isa
<CXXDestructorCall
>(Call
) ||
130 matchesAny(Call
, AppendFn
, AssignFn
, ClearFn
, EraseFn
, InsertFn
,
131 PopBackFn
, PushBackFn
, ReplaceFn
, ReserveFn
, ResizeFn
,
132 ShrinkToFitFn
, SwapFn
);
135 bool InnerPointerChecker::isInnerPointerAccessFunction(
136 const CallEvent
&Call
) const {
137 return matchesAny(Call
, CStrFn
, DataFn
, DataMemberFn
);
140 void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent
&Call
,
141 ProgramStateRef State
,
143 CheckerContext
&C
) const {
144 if (const PtrSet
*PS
= State
->get
<RawPtrMap
>(MR
)) {
145 const Expr
*Origin
= Call
.getOriginExpr();
146 for (const auto Symbol
: *PS
) {
147 // NOTE: `Origin` may be null, and will be stored so in the symbol's
148 // `RefState` in MallocChecker's `RegionState` program state map.
149 State
= allocation_state::markReleased(State
, Symbol
, Origin
);
151 State
= State
->remove
<RawPtrMap
>(MR
);
152 C
.addTransition(State
);
157 void InnerPointerChecker::checkFunctionArguments(const CallEvent
&Call
,
158 ProgramStateRef State
,
159 CheckerContext
&C
) const {
160 if (const auto *FC
= dyn_cast
<AnyFunctionCall
>(&Call
)) {
161 const FunctionDecl
*FD
= FC
->getDecl();
162 if (!FD
|| !FD
->isInStdNamespace())
165 for (unsigned I
= 0, E
= FD
->getNumParams(); I
!= E
; ++I
) {
166 QualType ParamTy
= FD
->getParamDecl(I
)->getType();
167 if (!ParamTy
->isReferenceType() ||
168 ParamTy
->getPointeeType().isConstQualified())
171 // In case of member operator calls, `this` is counted as an
172 // argument but not as a parameter.
173 bool isaMemberOpCall
= isa
<CXXMemberOperatorCall
>(FC
);
174 unsigned ArgI
= isaMemberOpCall
? I
+1 : I
;
176 SVal Arg
= FC
->getArgSVal(ArgI
);
177 const auto *ArgRegion
=
178 dyn_cast_or_null
<TypedValueRegion
>(Arg
.getAsRegion());
182 // std::addressof functions accepts a non-const reference as an argument,
183 // but doesn't modify it.
184 if (matchesAny(Call
, AddressofFn
, AddressofFn_
))
187 markPtrSymbolsReleased(Call
, State
, ArgRegion
, C
);
194 // "References, pointers, and iterators referring to the elements of a
195 // basic_string sequence may be invalidated by the following uses of that
196 // basic_string object:
198 // -- As an argument to any standard library function taking a reference
199 // to non-const basic_string as an argument. For example, as an argument to
200 // non-member functions swap(), operator>>(), and getline(), or as an argument
201 // to basic_string::swap().
203 // -- Calling non-const member functions, except operator[], at, front, back,
204 // begin, rbegin, end, and rend."
206 void InnerPointerChecker::checkPostCall(const CallEvent
&Call
,
207 CheckerContext
&C
) const {
208 ProgramStateRef State
= C
.getState();
210 // TODO: Do we need these to be typed?
211 const TypedValueRegion
*ObjRegion
= nullptr;
213 if (const auto *ICall
= dyn_cast
<CXXInstanceCall
>(&Call
)) {
214 ObjRegion
= dyn_cast_or_null
<TypedValueRegion
>(
215 ICall
->getCXXThisVal().getAsRegion());
217 // Check [string.require] / second point.
218 if (isInvalidatingMemberFunction(Call
)) {
219 markPtrSymbolsReleased(Call
, State
, ObjRegion
, C
);
224 if (isInnerPointerAccessFunction(Call
)) {
226 if (isa
<SimpleFunctionCall
>(Call
)) {
227 // NOTE: As of now, we only have one free access function: std::data.
228 // If we add more functions like this in the list, hardcoded
229 // argument index should be changed.
231 dyn_cast_or_null
<TypedValueRegion
>(Call
.getArgSVal(0).getAsRegion());
237 SVal RawPtr
= Call
.getReturnValue();
238 if (SymbolRef Sym
= RawPtr
.getAsSymbol(/*IncludeBaseRegions=*/true)) {
239 // Start tracking this raw pointer by adding it to the set of symbols
240 // associated with this container object in the program state map.
242 PtrSet::Factory
&F
= State
->getStateManager().get_context
<PtrSet
>();
243 const PtrSet
*SetPtr
= State
->get
<RawPtrMap
>(ObjRegion
);
244 PtrSet Set
= SetPtr
? *SetPtr
: F
.getEmptySet();
245 assert(C
.wasInlined
|| !Set
.contains(Sym
));
246 Set
= F
.add(Set
, Sym
);
248 State
= State
->set
<RawPtrMap
>(ObjRegion
, Set
);
249 C
.addTransition(State
);
255 // Check [string.require] / first point.
256 checkFunctionArguments(Call
, State
, C
);
259 void InnerPointerChecker::checkDeadSymbols(SymbolReaper
&SymReaper
,
260 CheckerContext
&C
) const {
261 ProgramStateRef State
= C
.getState();
262 PtrSet::Factory
&F
= State
->getStateManager().get_context
<PtrSet
>();
263 RawPtrMapTy RPM
= State
->get
<RawPtrMap
>();
264 for (const auto &Entry
: RPM
) {
265 if (!SymReaper
.isLiveRegion(Entry
.first
)) {
266 // Due to incomplete destructor support, some dead regions might
267 // remain in the program state map. Clean them up.
268 State
= State
->remove
<RawPtrMap
>(Entry
.first
);
270 if (const PtrSet
*OldSet
= State
->get
<RawPtrMap
>(Entry
.first
)) {
271 PtrSet CleanedUpSet
= *OldSet
;
272 for (const auto Symbol
: Entry
.second
) {
273 if (!SymReaper
.isLive(Symbol
))
274 CleanedUpSet
= F
.remove(CleanedUpSet
, Symbol
);
276 State
= CleanedUpSet
.isEmpty()
277 ? State
->remove
<RawPtrMap
>(Entry
.first
)
278 : State
->set
<RawPtrMap
>(Entry
.first
, CleanedUpSet
);
281 C
.addTransition(State
);
286 namespace allocation_state
{
288 std::unique_ptr
<BugReporterVisitor
> getInnerPointerBRVisitor(SymbolRef Sym
) {
289 return std::make_unique
<InnerPointerChecker::InnerPointerBRVisitor
>(Sym
);
292 const MemRegion
*getContainerObjRegion(ProgramStateRef State
, SymbolRef Sym
) {
293 RawPtrMapTy Map
= State
->get
<RawPtrMap
>();
294 for (const auto &Entry
: Map
) {
295 if (Entry
.second
.contains(Sym
)) {
302 } // end namespace allocation_state
303 } // end namespace ento
304 } // end namespace clang
306 PathDiagnosticPieceRef
InnerPointerChecker::InnerPointerBRVisitor::VisitNode(
307 const ExplodedNode
*N
, BugReporterContext
&BRC
, PathSensitiveBugReport
&) {
308 if (!isSymbolTracked(N
->getState(), PtrToBuf
) ||
309 isSymbolTracked(N
->getFirstPred()->getState(), PtrToBuf
))
312 const Stmt
*S
= N
->getStmtForDiagnostics();
316 const MemRegion
*ObjRegion
=
317 allocation_state::getContainerObjRegion(N
->getState(), PtrToBuf
);
318 const auto *TypedRegion
= cast
<TypedValueRegion
>(ObjRegion
);
319 QualType ObjTy
= TypedRegion
->getValueType();
321 SmallString
<256> Buf
;
322 llvm::raw_svector_ostream
OS(Buf
);
323 OS
<< "Pointer to inner buffer of '" << ObjTy
<< "' obtained here";
324 PathDiagnosticLocation
Pos(S
, BRC
.getSourceManager(),
325 N
->getLocationContext());
326 return std::make_shared
<PathDiagnosticEventPiece
>(Pos
, OS
.str(), true);
329 void ento::registerInnerPointerChecker(CheckerManager
&Mgr
) {
330 registerInnerPointerCheckerAux(Mgr
);
331 Mgr
.registerChecker
<InnerPointerChecker
>();
334 bool ento::shouldRegisterInnerPointerChecker(const CheckerManager
&mgr
) {