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