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 CallDescriptionSet InvalidatingMemberFunctions
{
39 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "append"}),
40 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "assign"}),
41 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "clear"}),
42 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "erase"}),
43 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "insert"}),
44 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "pop_back"}),
45 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "push_back"}),
46 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "replace"}),
47 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "reserve"}),
48 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "resize"}),
49 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "shrink_to_fit"}),
50 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "swap"})};
52 CallDescriptionSet AddressofFunctions
{
53 CallDescription(CDM::SimpleFunc
, {"std", "addressof"}),
54 CallDescription(CDM::SimpleFunc
, {"std", "__addressof"})};
56 CallDescriptionSet InnerPointerAccessFunctions
{
57 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "c_str"}),
58 CallDescription(CDM::SimpleFunc
, {"std", "data"}, 1),
59 CallDescription(CDM::CXXMethod
, {"std", "basic_string", "data"})};
62 class InnerPointerBRVisitor
: public BugReporterVisitor
{
66 InnerPointerBRVisitor(SymbolRef Sym
) : PtrToBuf(Sym
) {}
68 static void *getTag() {
73 void Profile(llvm::FoldingSetNodeID
&ID
) const override
{
74 ID
.AddPointer(getTag());
77 PathDiagnosticPieceRef
VisitNode(const ExplodedNode
*N
,
78 BugReporterContext
&BRC
,
79 PathSensitiveBugReport
&BR
) override
;
81 // FIXME: Scan the map once in the visitor's constructor and do a direct
83 bool isSymbolTracked(ProgramStateRef State
, SymbolRef Sym
) {
84 RawPtrMapTy Map
= State
->get
<RawPtrMap
>();
85 for (const auto &Entry
: Map
) {
86 if (Entry
.second
.contains(Sym
))
93 /// Check whether the called member function potentially invalidates
94 /// pointers referring to the container object's inner buffer.
95 bool isInvalidatingMemberFunction(const CallEvent
&Call
) const;
97 /// Mark pointer symbols associated with the given memory region released
98 /// in the program state.
99 void markPtrSymbolsReleased(const CallEvent
&Call
, ProgramStateRef State
,
100 const MemRegion
*ObjRegion
,
101 CheckerContext
&C
) const;
103 /// Standard library functions that take a non-const `basic_string` argument by
104 /// reference may invalidate its inner pointers. Check for these cases and
105 /// mark the pointers released.
106 void checkFunctionArguments(const CallEvent
&Call
, ProgramStateRef State
,
107 CheckerContext
&C
) const;
109 /// Record the connection between raw pointers referring to a container
110 /// object's inner buffer and the object's memory region in the program state.
111 /// Mark potentially invalidated pointers released.
112 void checkPostCall(const CallEvent
&Call
, CheckerContext
&C
) const;
114 /// Clean up the program state map.
115 void checkDeadSymbols(SymbolReaper
&SymReaper
, CheckerContext
&C
) const;
118 } // end anonymous namespace
120 bool InnerPointerChecker::isInvalidatingMemberFunction(
121 const CallEvent
&Call
) const {
122 if (const auto *MemOpCall
= dyn_cast
<CXXMemberOperatorCall
>(&Call
)) {
123 OverloadedOperatorKind Opc
= MemOpCall
->getOriginExpr()->getOperator();
124 if (Opc
== OO_Equal
|| Opc
== OO_PlusEqual
)
128 return isa
<CXXDestructorCall
>(Call
) ||
129 InvalidatingMemberFunctions
.contains(Call
);
132 void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent
&Call
,
133 ProgramStateRef State
,
135 CheckerContext
&C
) const {
136 if (const PtrSet
*PS
= State
->get
<RawPtrMap
>(MR
)) {
137 const Expr
*Origin
= Call
.getOriginExpr();
138 for (const auto Symbol
: *PS
) {
139 // NOTE: `Origin` may be null, and will be stored so in the symbol's
140 // `RefState` in MallocChecker's `RegionState` program state map.
141 State
= allocation_state::markReleased(State
, Symbol
, Origin
);
143 State
= State
->remove
<RawPtrMap
>(MR
);
144 C
.addTransition(State
);
149 void InnerPointerChecker::checkFunctionArguments(const CallEvent
&Call
,
150 ProgramStateRef State
,
151 CheckerContext
&C
) const {
152 if (const auto *FC
= dyn_cast
<AnyFunctionCall
>(&Call
)) {
153 const FunctionDecl
*FD
= FC
->getDecl();
154 if (!FD
|| !FD
->isInStdNamespace())
157 for (unsigned I
= 0, E
= FD
->getNumParams(); I
!= E
; ++I
) {
158 QualType ParamTy
= FD
->getParamDecl(I
)->getType();
159 if (!ParamTy
->isReferenceType() ||
160 ParamTy
->getPointeeType().isConstQualified())
163 // In case of member operator calls, `this` is counted as an
164 // argument but not as a parameter.
165 bool isaMemberOpCall
= isa
<CXXMemberOperatorCall
>(FC
);
166 unsigned ArgI
= isaMemberOpCall
? I
+1 : I
;
168 SVal Arg
= FC
->getArgSVal(ArgI
);
169 const auto *ArgRegion
=
170 dyn_cast_or_null
<TypedValueRegion
>(Arg
.getAsRegion());
174 // std::addressof functions accepts a non-const reference as an argument,
175 // but doesn't modify it.
176 if (AddressofFunctions
.contains(Call
))
179 markPtrSymbolsReleased(Call
, State
, ArgRegion
, C
);
186 // "References, pointers, and iterators referring to the elements of a
187 // basic_string sequence may be invalidated by the following uses of that
188 // basic_string object:
190 // -- As an argument to any standard library function taking a reference
191 // to non-const basic_string as an argument. For example, as an argument to
192 // non-member functions swap(), operator>>(), and getline(), or as an argument
193 // to basic_string::swap().
195 // -- Calling non-const member functions, except operator[], at, front, back,
196 // begin, rbegin, end, and rend."
198 void InnerPointerChecker::checkPostCall(const CallEvent
&Call
,
199 CheckerContext
&C
) const {
200 ProgramStateRef State
= C
.getState();
202 // TODO: Do we need these to be typed?
203 const TypedValueRegion
*ObjRegion
= nullptr;
205 if (const auto *ICall
= dyn_cast
<CXXInstanceCall
>(&Call
)) {
206 ObjRegion
= dyn_cast_or_null
<TypedValueRegion
>(
207 ICall
->getCXXThisVal().getAsRegion());
209 // Check [string.require] / second point.
210 if (isInvalidatingMemberFunction(Call
)) {
211 markPtrSymbolsReleased(Call
, State
, ObjRegion
, C
);
216 if (InnerPointerAccessFunctions
.contains(Call
)) {
218 if (isa
<SimpleFunctionCall
>(Call
)) {
219 // NOTE: As of now, we only have one free access function: std::data.
220 // If we add more functions like this in the list, hardcoded
221 // argument index should be changed.
223 dyn_cast_or_null
<TypedValueRegion
>(Call
.getArgSVal(0).getAsRegion());
229 SVal RawPtr
= Call
.getReturnValue();
230 if (SymbolRef Sym
= RawPtr
.getAsSymbol(/*IncludeBaseRegions=*/true)) {
231 // Start tracking this raw pointer by adding it to the set of symbols
232 // associated with this container object in the program state map.
234 PtrSet::Factory
&F
= State
->getStateManager().get_context
<PtrSet
>();
235 const PtrSet
*SetPtr
= State
->get
<RawPtrMap
>(ObjRegion
);
236 PtrSet Set
= SetPtr
? *SetPtr
: F
.getEmptySet();
237 assert(C
.wasInlined
|| !Set
.contains(Sym
));
238 Set
= F
.add(Set
, Sym
);
240 State
= State
->set
<RawPtrMap
>(ObjRegion
, Set
);
241 C
.addTransition(State
);
247 // Check [string.require] / first point.
248 checkFunctionArguments(Call
, State
, C
);
251 void InnerPointerChecker::checkDeadSymbols(SymbolReaper
&SymReaper
,
252 CheckerContext
&C
) const {
253 ProgramStateRef State
= C
.getState();
254 PtrSet::Factory
&F
= State
->getStateManager().get_context
<PtrSet
>();
255 RawPtrMapTy RPM
= State
->get
<RawPtrMap
>();
256 for (const auto &Entry
: RPM
) {
257 if (!SymReaper
.isLiveRegion(Entry
.first
)) {
258 // Due to incomplete destructor support, some dead regions might
259 // remain in the program state map. Clean them up.
260 State
= State
->remove
<RawPtrMap
>(Entry
.first
);
262 if (const PtrSet
*OldSet
= State
->get
<RawPtrMap
>(Entry
.first
)) {
263 PtrSet CleanedUpSet
= *OldSet
;
264 for (const auto Symbol
: Entry
.second
) {
265 if (!SymReaper
.isLive(Symbol
))
266 CleanedUpSet
= F
.remove(CleanedUpSet
, Symbol
);
268 State
= CleanedUpSet
.isEmpty()
269 ? State
->remove
<RawPtrMap
>(Entry
.first
)
270 : State
->set
<RawPtrMap
>(Entry
.first
, CleanedUpSet
);
273 C
.addTransition(State
);
278 namespace allocation_state
{
280 std::unique_ptr
<BugReporterVisitor
> getInnerPointerBRVisitor(SymbolRef Sym
) {
281 return std::make_unique
<InnerPointerChecker::InnerPointerBRVisitor
>(Sym
);
284 const MemRegion
*getContainerObjRegion(ProgramStateRef State
, SymbolRef Sym
) {
285 RawPtrMapTy Map
= State
->get
<RawPtrMap
>();
286 for (const auto &Entry
: Map
) {
287 if (Entry
.second
.contains(Sym
)) {
294 } // end namespace allocation_state
295 } // end namespace ento
296 } // end namespace clang
298 PathDiagnosticPieceRef
InnerPointerChecker::InnerPointerBRVisitor::VisitNode(
299 const ExplodedNode
*N
, BugReporterContext
&BRC
, PathSensitiveBugReport
&) {
300 if (!isSymbolTracked(N
->getState(), PtrToBuf
) ||
301 isSymbolTracked(N
->getFirstPred()->getState(), PtrToBuf
))
304 const Stmt
*S
= N
->getStmtForDiagnostics();
308 const MemRegion
*ObjRegion
=
309 allocation_state::getContainerObjRegion(N
->getState(), PtrToBuf
);
310 const auto *TypedRegion
= cast
<TypedValueRegion
>(ObjRegion
);
311 QualType ObjTy
= TypedRegion
->getValueType();
313 SmallString
<256> Buf
;
314 llvm::raw_svector_ostream
OS(Buf
);
315 OS
<< "Pointer to inner buffer of '" << ObjTy
<< "' obtained here";
316 PathDiagnosticLocation
Pos(S
, BRC
.getSourceManager(),
317 N
->getLocationContext());
318 return std::make_shared
<PathDiagnosticEventPiece
>(Pos
, OS
.str(), true);
321 void ento::registerInnerPointerChecker(CheckerManager
&Mgr
) {
322 registerInnerPointerCheckerAux(Mgr
);
323 Mgr
.registerChecker
<InnerPointerChecker
>();
326 bool ento::shouldRegisterInnerPointerChecker(const CheckerManager
&mgr
) {