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