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