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