/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //=== InnerPointerChecker.cpp -------------------------------------*- C++ -*--// |
2 | | // |
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 |
6 | | // |
7 | | //===----------------------------------------------------------------------===// |
8 | | // |
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. |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #include "AllocationState.h" |
16 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
17 | | #include "InterCheckerAPI.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/CallEvent.h" |
22 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
23 | | |
24 | | using namespace clang; |
25 | | using namespace ento; |
26 | | |
27 | | // Associate container objects with a set of raw pointer symbols. |
28 | | REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(PtrSet, SymbolRef) |
29 | | REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet) |
30 | | |
31 | | |
32 | | namespace { |
33 | | |
34 | | class InnerPointerChecker |
35 | | : public Checker<check::DeadSymbols, check::PostCall> { |
36 | | |
37 | | CallDescription AppendFn, AssignFn, ClearFn, CStrFn, DataFn, EraseFn, |
38 | | InsertFn, PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn, |
39 | | ShrinkToFitFn, SwapFn; |
40 | | |
41 | | public: |
42 | | class InnerPointerBRVisitor : public BugReporterVisitor { |
43 | | SymbolRef PtrToBuf; |
44 | | |
45 | | public: |
46 | 30 | InnerPointerBRVisitor(SymbolRef Sym) : PtrToBuf(Sym) {} |
47 | | |
48 | 30 | static void *getTag() { |
49 | 30 | static int Tag = 0; |
50 | 30 | return &Tag; |
51 | 30 | } |
52 | | |
53 | 30 | void Profile(llvm::FoldingSetNodeID &ID) const override { |
54 | 30 | ID.AddPointer(getTag()); |
55 | 30 | } |
56 | | |
57 | | virtual PathDiagnosticPieceRef |
58 | | VisitNode(const ExplodedNode *N, BugReporterContext &BRC, |
59 | | PathSensitiveBugReport &BR) override; |
60 | | |
61 | | // FIXME: Scan the map once in the visitor's constructor and do a direct |
62 | | // lookup by region. |
63 | 2.06k | bool isSymbolTracked(ProgramStateRef State, SymbolRef Sym) { |
64 | 2.06k | RawPtrMapTy Map = State->get<RawPtrMap>(); |
65 | 1.07k | for (const auto &Entry : Map) { |
66 | 1.07k | if (Entry.second.contains(Sym)) |
67 | 934 | return true; |
68 | 1.07k | } |
69 | 1.13k | return false; |
70 | 2.06k | } |
71 | | }; |
72 | | |
73 | | InnerPointerChecker() |
74 | | : AppendFn({"std", "basic_string", "append"}), |
75 | | AssignFn({"std", "basic_string", "assign"}), |
76 | | ClearFn({"std", "basic_string", "clear"}), |
77 | | CStrFn({"std", "basic_string", "c_str"}), |
78 | | DataFn({"std", "basic_string", "data"}), |
79 | | EraseFn({"std", "basic_string", "erase"}), |
80 | | InsertFn({"std", "basic_string", "insert"}), |
81 | | PopBackFn({"std", "basic_string", "pop_back"}), |
82 | | PushBackFn({"std", "basic_string", "push_back"}), |
83 | | ReplaceFn({"std", "basic_string", "replace"}), |
84 | | ReserveFn({"std", "basic_string", "reserve"}), |
85 | | ResizeFn({"std", "basic_string", "resize"}), |
86 | | ShrinkToFitFn({"std", "basic_string", "shrink_to_fit"}), |
87 | 60 | SwapFn({"std", "basic_string", "swap"}) {} |
88 | | |
89 | | /// Check whether the called member function potentially invalidates |
90 | | /// pointers referring to the container object's inner buffer. |
91 | | bool isInvalidatingMemberFunction(const CallEvent &Call) const; |
92 | | |
93 | | /// Mark pointer symbols associated with the given memory region released |
94 | | /// in the program state. |
95 | | void markPtrSymbolsReleased(const CallEvent &Call, ProgramStateRef State, |
96 | | const MemRegion *ObjRegion, |
97 | | CheckerContext &C) const; |
98 | | |
99 | | /// Standard library functions that take a non-const `basic_string` argument by |
100 | | /// reference may invalidate its inner pointers. Check for these cases and |
101 | | /// mark the pointers released. |
102 | | void checkFunctionArguments(const CallEvent &Call, ProgramStateRef State, |
103 | | CheckerContext &C) const; |
104 | | |
105 | | /// Record the connection between raw pointers referring to a container |
106 | | /// object's inner buffer and the object's memory region in the program state. |
107 | | /// Mark potentially invalidated pointers released. |
108 | | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
109 | | |
110 | | /// Clean up the program state map. |
111 | | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
112 | | }; |
113 | | |
114 | | } // end anonymous namespace |
115 | | |
116 | | bool InnerPointerChecker::isInvalidatingMemberFunction( |
117 | 2.24k | const CallEvent &Call) const { |
118 | 2.24k | if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) { |
119 | 1.28k | OverloadedOperatorKind Opc = MemOpCall->getOriginExpr()->getOperator(); |
120 | 1.28k | if (Opc == OO_Equal || Opc == OO_PlusEqual1.27k ) |
121 | 167 | return true; |
122 | 1.11k | return false; |
123 | 1.28k | } |
124 | 958 | return (isa<CXXDestructorCall>(Call) || Call.isCalled(AppendFn)543 || |
125 | 542 | Call.isCalled(AssignFn) || Call.isCalled(ClearFn)541 || |
126 | 539 | Call.isCalled(EraseFn) || Call.isCalled(InsertFn)538 || |
127 | 537 | Call.isCalled(PopBackFn) || Call.isCalled(PushBackFn)536 || |
128 | 535 | Call.isCalled(ReplaceFn) || Call.isCalled(ReserveFn)534 || |
129 | 533 | Call.isCalled(ResizeFn) || Call.isCalled(ShrinkToFitFn)532 || |
130 | 531 | Call.isCalled(SwapFn)); |
131 | 2.24k | } |
132 | | |
133 | | void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent &Call, |
134 | | ProgramStateRef State, |
135 | | const MemRegion *MR, |
136 | 778 | CheckerContext &C) const { |
137 | 778 | if (const PtrSet *PS = State->get<RawPtrMap>(MR)) { |
138 | 32 | const Expr *Origin = Call.getOriginExpr(); |
139 | 36 | for (const auto Symbol : *PS) { |
140 | | // NOTE: `Origin` may be null, and will be stored so in the symbol's |
141 | | // `RefState` in MallocChecker's `RegionState` program state map. |
142 | 36 | State = allocation_state::markReleased(State, Symbol, Origin); |
143 | 36 | } |
144 | 32 | State = State->remove<RawPtrMap>(MR); |
145 | 32 | C.addTransition(State); |
146 | 32 | return; |
147 | 32 | } |
148 | 778 | } |
149 | | |
150 | | void InnerPointerChecker::checkFunctionArguments(const CallEvent &Call, |
151 | | ProgramStateRef State, |
152 | 28.9k | CheckerContext &C) const { |
153 | 28.9k | if (const auto *FC = dyn_cast<AnyFunctionCall>(&Call)) { |
154 | 28.9k | const FunctionDecl *FD = FC->getDecl(); |
155 | 28.9k | if (!FD || !FD->isInStdNamespace()28.9k ) |
156 | 28.4k | return; |
157 | | |
158 | 1.52k | for (unsigned I = 0, E = FD->getNumParams(); 429 I != E; ++I1.09k ) { |
159 | 1.09k | QualType ParamTy = FD->getParamDecl(I)->getType(); |
160 | 1.09k | if (!ParamTy->isReferenceType() || |
161 | 285 | ParamTy->getPointeeType().isConstQualified()) |
162 | 881 | continue; |
163 | | |
164 | | // In case of member operator calls, `this` is counted as an |
165 | | // argument but not as a parameter. |
166 | 210 | bool isaMemberOpCall = isa<CXXMemberOperatorCall>(FC); |
167 | 210 | unsigned ArgI = isaMemberOpCall ? I+10 : I; |
168 | | |
169 | 210 | SVal Arg = FC->getArgSVal(ArgI); |
170 | 210 | const auto *ArgRegion = |
171 | 210 | dyn_cast_or_null<TypedValueRegion>(Arg.getAsRegion()); |
172 | 210 | if (!ArgRegion) |
173 | 27 | continue; |
174 | | |
175 | 183 | markPtrSymbolsReleased(Call, State, ArgRegion, C); |
176 | 183 | } |
177 | 429 | } |
178 | 28.9k | } |
179 | | |
180 | | // [string.require] |
181 | | // |
182 | | // "References, pointers, and iterators referring to the elements of a |
183 | | // basic_string sequence may be invalidated by the following uses of that |
184 | | // basic_string object: |
185 | | // |
186 | | // -- As an argument to any standard library function taking a reference |
187 | | // to non-const basic_string as an argument. For example, as an argument to |
188 | | // non-member functions swap(), operator>>(), and getline(), or as an argument |
189 | | // to basic_string::swap(). |
190 | | // |
191 | | // -- Calling non-const member functions, except operator[], at, front, back, |
192 | | // begin, rbegin, end, and rend." |
193 | | |
194 | | void InnerPointerChecker::checkPostCall(const CallEvent &Call, |
195 | 32.6k | CheckerContext &C) const { |
196 | 32.6k | ProgramStateRef State = C.getState(); |
197 | | |
198 | 32.6k | if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) { |
199 | | // TODO: Do we need these to be typed? |
200 | 5.34k | const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>( |
201 | 5.34k | ICall->getCXXThisVal().getAsRegion()); |
202 | 5.34k | if (!ObjRegion) |
203 | 3.06k | return; |
204 | | |
205 | 2.28k | if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)2.25k ) { |
206 | 42 | SVal RawPtr = Call.getReturnValue(); |
207 | 42 | if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) { |
208 | | // Start tracking this raw pointer by adding it to the set of symbols |
209 | | // associated with this container object in the program state map. |
210 | | |
211 | 42 | PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); |
212 | 42 | const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion); |
213 | 42 | PtrSet Set = SetPtr ? *SetPtr5 : F.getEmptySet()37 ; |
214 | 42 | assert(C.wasInlined || !Set.contains(Sym)); |
215 | 0 | Set = F.add(Set, Sym); |
216 | | |
217 | 42 | State = State->set<RawPtrMap>(ObjRegion, Set); |
218 | 42 | C.addTransition(State); |
219 | 42 | } |
220 | 0 | return; |
221 | 42 | } |
222 | | |
223 | | // Check [string.require] / second point. |
224 | 2.24k | if (isInvalidatingMemberFunction(Call)) { |
225 | 595 | markPtrSymbolsReleased(Call, State, ObjRegion, C); |
226 | 595 | return; |
227 | 595 | } |
228 | 2.24k | } |
229 | | |
230 | | // Check [string.require] / first point. |
231 | 28.9k | checkFunctionArguments(Call, State, C); |
232 | 28.9k | } |
233 | | |
234 | | void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
235 | 96.6k | CheckerContext &C) const { |
236 | 96.6k | ProgramStateRef State = C.getState(); |
237 | 96.6k | PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); |
238 | 96.6k | RawPtrMapTy RPM = State->get<RawPtrMap>(); |
239 | 95 | for (const auto &Entry : RPM) { |
240 | 95 | if (!SymReaper.isLiveRegion(Entry.first)) { |
241 | | // Due to incomplete destructor support, some dead regions might |
242 | | // remain in the program state map. Clean them up. |
243 | 0 | State = State->remove<RawPtrMap>(Entry.first); |
244 | 0 | } |
245 | 95 | if (const PtrSet *OldSet = State->get<RawPtrMap>(Entry.first)) { |
246 | 95 | PtrSet CleanedUpSet = *OldSet; |
247 | 106 | for (const auto Symbol : Entry.second) { |
248 | 106 | if (!SymReaper.isLive(Symbol)) |
249 | 11 | CleanedUpSet = F.remove(CleanedUpSet, Symbol); |
250 | 106 | } |
251 | 95 | State = CleanedUpSet.isEmpty() |
252 | 9 | ? State->remove<RawPtrMap>(Entry.first) |
253 | 86 | : State->set<RawPtrMap>(Entry.first, CleanedUpSet); |
254 | 95 | } |
255 | 95 | } |
256 | 96.6k | C.addTransition(State); |
257 | 96.6k | } |
258 | | |
259 | | namespace clang { |
260 | | namespace ento { |
261 | | namespace allocation_state { |
262 | | |
263 | 30 | std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { |
264 | 30 | return std::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym); |
265 | 30 | } |
266 | | |
267 | 60 | const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { |
268 | 60 | RawPtrMapTy Map = State->get<RawPtrMap>(); |
269 | 60 | for (const auto &Entry : Map) { |
270 | 60 | if (Entry.second.contains(Sym)) { |
271 | 60 | return Entry.first; |
272 | 60 | } |
273 | 60 | } |
274 | 0 | return nullptr; |
275 | 60 | } |
276 | | |
277 | | } // end namespace allocation_state |
278 | | } // end namespace ento |
279 | | } // end namespace clang |
280 | | |
281 | | PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( |
282 | 1.58k | const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { |
283 | 1.58k | if (!isSymbolTracked(N->getState(), PtrToBuf) || |
284 | 482 | isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) |
285 | 1.55k | return nullptr; |
286 | | |
287 | 30 | const Stmt *S = N->getStmtForDiagnostics(); |
288 | 30 | if (!S) |
289 | 0 | return nullptr; |
290 | | |
291 | 30 | const MemRegion *ObjRegion = |
292 | 30 | allocation_state::getContainerObjRegion(N->getState(), PtrToBuf); |
293 | 30 | const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion); |
294 | 30 | QualType ObjTy = TypedRegion->getValueType(); |
295 | | |
296 | 30 | SmallString<256> Buf; |
297 | 30 | llvm::raw_svector_ostream OS(Buf); |
298 | 30 | OS << "Pointer to inner buffer of '" << ObjTy.getAsString() |
299 | 30 | << "' obtained here"; |
300 | 30 | PathDiagnosticLocation Pos(S, BRC.getSourceManager(), |
301 | 30 | N->getLocationContext()); |
302 | 30 | return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true); |
303 | 30 | } |
304 | | |
305 | 60 | void ento::registerInnerPointerChecker(CheckerManager &Mgr) { |
306 | 60 | registerInnerPointerCheckerAux(Mgr); |
307 | 60 | Mgr.registerChecker<InnerPointerChecker>(); |
308 | 60 | } |
309 | | |
310 | 120 | bool ento::shouldRegisterInnerPointerChecker(const CheckerManager &mgr) { |
311 | 120 | return true; |
312 | 120 | } |