xref: /aosp_15_r20/external/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp (revision 67e74705e28f6214e480b399dd47ea732279e315)
1*67e74705SXin Li //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
2*67e74705SXin Li //
3*67e74705SXin Li //                     The LLVM Compiler Infrastructure
4*67e74705SXin Li //
5*67e74705SXin Li // This file is distributed under the University of Illinois Open Source
6*67e74705SXin Li // License. See LICENSE.TXT for details.
7*67e74705SXin Li //
8*67e74705SXin Li //===----------------------------------------------------------------------===//
9*67e74705SXin Li //
10*67e74705SXin Li // Defines a checker for proper use of fopen/fclose APIs.
11*67e74705SXin Li //   - If a file has been closed with fclose, it should not be accessed again.
12*67e74705SXin Li //   Accessing a closed file results in undefined behavior.
13*67e74705SXin Li //   - If a file was opened with fopen, it must be closed with fclose before
14*67e74705SXin Li //   the execution ends. Failing to do so results in a resource leak.
15*67e74705SXin Li //
16*67e74705SXin Li //===----------------------------------------------------------------------===//
17*67e74705SXin Li 
18*67e74705SXin Li #include "ClangSACheckers.h"
19*67e74705SXin Li #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
20*67e74705SXin Li #include "clang/StaticAnalyzer/Core/Checker.h"
21*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22*67e74705SXin Li #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23*67e74705SXin Li #include <utility>
24*67e74705SXin Li 
25*67e74705SXin Li using namespace clang;
26*67e74705SXin Li using namespace ento;
27*67e74705SXin Li 
28*67e74705SXin Li namespace {
29*67e74705SXin Li typedef SmallVector<SymbolRef, 2> SymbolVector;
30*67e74705SXin Li 
31*67e74705SXin Li struct StreamState {
32*67e74705SXin Li private:
33*67e74705SXin Li   enum Kind { Opened, Closed } K;
StreamState__anon49532eb10111::StreamState34*67e74705SXin Li   StreamState(Kind InK) : K(InK) { }
35*67e74705SXin Li 
36*67e74705SXin Li public:
isOpened__anon49532eb10111::StreamState37*67e74705SXin Li   bool isOpened() const { return K == Opened; }
isClosed__anon49532eb10111::StreamState38*67e74705SXin Li   bool isClosed() const { return K == Closed; }
39*67e74705SXin Li 
getOpened__anon49532eb10111::StreamState40*67e74705SXin Li   static StreamState getOpened() { return StreamState(Opened); }
getClosed__anon49532eb10111::StreamState41*67e74705SXin Li   static StreamState getClosed() { return StreamState(Closed); }
42*67e74705SXin Li 
operator ==__anon49532eb10111::StreamState43*67e74705SXin Li   bool operator==(const StreamState &X) const {
44*67e74705SXin Li     return K == X.K;
45*67e74705SXin Li   }
Profile__anon49532eb10111::StreamState46*67e74705SXin Li   void Profile(llvm::FoldingSetNodeID &ID) const {
47*67e74705SXin Li     ID.AddInteger(K);
48*67e74705SXin Li   }
49*67e74705SXin Li };
50*67e74705SXin Li 
51*67e74705SXin Li class SimpleStreamChecker : public Checker<check::PostCall,
52*67e74705SXin Li                                            check::PreCall,
53*67e74705SXin Li                                            check::DeadSymbols,
54*67e74705SXin Li                                            check::PointerEscape> {
55*67e74705SXin Li   CallDescription OpenFn, CloseFn;
56*67e74705SXin Li 
57*67e74705SXin Li   std::unique_ptr<BugType> DoubleCloseBugType;
58*67e74705SXin Li   std::unique_ptr<BugType> LeakBugType;
59*67e74705SXin Li 
60*67e74705SXin Li   void reportDoubleClose(SymbolRef FileDescSym,
61*67e74705SXin Li                          const CallEvent &Call,
62*67e74705SXin Li                          CheckerContext &C) const;
63*67e74705SXin Li 
64*67e74705SXin Li   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
65*67e74705SXin Li                    ExplodedNode *ErrNode) const;
66*67e74705SXin Li 
67*67e74705SXin Li   bool guaranteedNotToCloseFile(const CallEvent &Call) const;
68*67e74705SXin Li 
69*67e74705SXin Li public:
70*67e74705SXin Li   SimpleStreamChecker();
71*67e74705SXin Li 
72*67e74705SXin Li   /// Process fopen.
73*67e74705SXin Li   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
74*67e74705SXin Li   /// Process fclose.
75*67e74705SXin Li   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
76*67e74705SXin Li 
77*67e74705SXin Li   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
78*67e74705SXin Li 
79*67e74705SXin Li   /// Stop tracking addresses which escape.
80*67e74705SXin Li   ProgramStateRef checkPointerEscape(ProgramStateRef State,
81*67e74705SXin Li                                     const InvalidatedSymbols &Escaped,
82*67e74705SXin Li                                     const CallEvent *Call,
83*67e74705SXin Li                                     PointerEscapeKind Kind) const;
84*67e74705SXin Li };
85*67e74705SXin Li 
86*67e74705SXin Li } // end anonymous namespace
87*67e74705SXin Li 
88*67e74705SXin Li /// The state of the checker is a map from tracked stream symbols to their
89*67e74705SXin Li /// state. Let's store it in the ProgramState.
90*67e74705SXin Li REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
91*67e74705SXin Li 
92*67e74705SXin Li namespace {
93*67e74705SXin Li class StopTrackingCallback final : public SymbolVisitor {
94*67e74705SXin Li   ProgramStateRef state;
95*67e74705SXin Li public:
StopTrackingCallback(ProgramStateRef st)96*67e74705SXin Li   StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
getState() const97*67e74705SXin Li   ProgramStateRef getState() const { return state; }
98*67e74705SXin Li 
VisitSymbol(SymbolRef sym)99*67e74705SXin Li   bool VisitSymbol(SymbolRef sym) override {
100*67e74705SXin Li     state = state->remove<StreamMap>(sym);
101*67e74705SXin Li     return true;
102*67e74705SXin Li   }
103*67e74705SXin Li };
104*67e74705SXin Li } // end anonymous namespace
105*67e74705SXin Li 
SimpleStreamChecker()106*67e74705SXin Li SimpleStreamChecker::SimpleStreamChecker()
107*67e74705SXin Li     : OpenFn("fopen"), CloseFn("fclose", 1) {
108*67e74705SXin Li   // Initialize the bug types.
109*67e74705SXin Li   DoubleCloseBugType.reset(
110*67e74705SXin Li       new BugType(this, "Double fclose", "Unix Stream API Error"));
111*67e74705SXin Li 
112*67e74705SXin Li   LeakBugType.reset(
113*67e74705SXin Li       new BugType(this, "Resource Leak", "Unix Stream API Error"));
114*67e74705SXin Li   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
115*67e74705SXin Li   LeakBugType->setSuppressOnSink(true);
116*67e74705SXin Li }
117*67e74705SXin Li 
checkPostCall(const CallEvent & Call,CheckerContext & C) const118*67e74705SXin Li void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
119*67e74705SXin Li                                         CheckerContext &C) const {
120*67e74705SXin Li   if (!Call.isGlobalCFunction())
121*67e74705SXin Li     return;
122*67e74705SXin Li 
123*67e74705SXin Li   if (!Call.isCalled(OpenFn))
124*67e74705SXin Li     return;
125*67e74705SXin Li 
126*67e74705SXin Li   // Get the symbolic value corresponding to the file handle.
127*67e74705SXin Li   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
128*67e74705SXin Li   if (!FileDesc)
129*67e74705SXin Li     return;
130*67e74705SXin Li 
131*67e74705SXin Li   // Generate the next transition (an edge in the exploded graph).
132*67e74705SXin Li   ProgramStateRef State = C.getState();
133*67e74705SXin Li   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
134*67e74705SXin Li   C.addTransition(State);
135*67e74705SXin Li }
136*67e74705SXin Li 
checkPreCall(const CallEvent & Call,CheckerContext & C) const137*67e74705SXin Li void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
138*67e74705SXin Li                                        CheckerContext &C) const {
139*67e74705SXin Li   if (!Call.isGlobalCFunction())
140*67e74705SXin Li     return;
141*67e74705SXin Li 
142*67e74705SXin Li   if (!Call.isCalled(CloseFn))
143*67e74705SXin Li     return;
144*67e74705SXin Li 
145*67e74705SXin Li   // Get the symbolic value corresponding to the file handle.
146*67e74705SXin Li   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
147*67e74705SXin Li   if (!FileDesc)
148*67e74705SXin Li     return;
149*67e74705SXin Li 
150*67e74705SXin Li   // Check if the stream has already been closed.
151*67e74705SXin Li   ProgramStateRef State = C.getState();
152*67e74705SXin Li   const StreamState *SS = State->get<StreamMap>(FileDesc);
153*67e74705SXin Li   if (SS && SS->isClosed()) {
154*67e74705SXin Li     reportDoubleClose(FileDesc, Call, C);
155*67e74705SXin Li     return;
156*67e74705SXin Li   }
157*67e74705SXin Li 
158*67e74705SXin Li   // Generate the next transition, in which the stream is closed.
159*67e74705SXin Li   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
160*67e74705SXin Li   C.addTransition(State);
161*67e74705SXin Li }
162*67e74705SXin Li 
isLeaked(SymbolRef Sym,const StreamState & SS,bool IsSymDead,ProgramStateRef State)163*67e74705SXin Li static bool isLeaked(SymbolRef Sym, const StreamState &SS,
164*67e74705SXin Li                      bool IsSymDead, ProgramStateRef State) {
165*67e74705SXin Li   if (IsSymDead && SS.isOpened()) {
166*67e74705SXin Li     // If a symbol is NULL, assume that fopen failed on this path.
167*67e74705SXin Li     // A symbol should only be considered leaked if it is non-null.
168*67e74705SXin Li     ConstraintManager &CMgr = State->getConstraintManager();
169*67e74705SXin Li     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
170*67e74705SXin Li     return !OpenFailed.isConstrainedTrue();
171*67e74705SXin Li   }
172*67e74705SXin Li   return false;
173*67e74705SXin Li }
174*67e74705SXin Li 
checkDeadSymbols(SymbolReaper & SymReaper,CheckerContext & C) const175*67e74705SXin Li void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
176*67e74705SXin Li                                            CheckerContext &C) const {
177*67e74705SXin Li   ProgramStateRef State = C.getState();
178*67e74705SXin Li   SymbolVector LeakedStreams;
179*67e74705SXin Li   StreamMapTy TrackedStreams = State->get<StreamMap>();
180*67e74705SXin Li   for (StreamMapTy::iterator I = TrackedStreams.begin(),
181*67e74705SXin Li                              E = TrackedStreams.end(); I != E; ++I) {
182*67e74705SXin Li     SymbolRef Sym = I->first;
183*67e74705SXin Li     bool IsSymDead = SymReaper.isDead(Sym);
184*67e74705SXin Li 
185*67e74705SXin Li     // Collect leaked symbols.
186*67e74705SXin Li     if (isLeaked(Sym, I->second, IsSymDead, State))
187*67e74705SXin Li       LeakedStreams.push_back(Sym);
188*67e74705SXin Li 
189*67e74705SXin Li     // Remove the dead symbol from the streams map.
190*67e74705SXin Li     if (IsSymDead)
191*67e74705SXin Li       State = State->remove<StreamMap>(Sym);
192*67e74705SXin Li   }
193*67e74705SXin Li 
194*67e74705SXin Li   ExplodedNode *N = C.generateNonFatalErrorNode(State);
195*67e74705SXin Li   if (!N)
196*67e74705SXin Li     return;
197*67e74705SXin Li   reportLeaks(LeakedStreams, C, N);
198*67e74705SXin Li }
199*67e74705SXin Li 
reportDoubleClose(SymbolRef FileDescSym,const CallEvent & Call,CheckerContext & C) const200*67e74705SXin Li void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
201*67e74705SXin Li                                             const CallEvent &Call,
202*67e74705SXin Li                                             CheckerContext &C) const {
203*67e74705SXin Li   // We reached a bug, stop exploring the path here by generating a sink.
204*67e74705SXin Li   ExplodedNode *ErrNode = C.generateErrorNode();
205*67e74705SXin Li   // If we've already reached this node on another path, return.
206*67e74705SXin Li   if (!ErrNode)
207*67e74705SXin Li     return;
208*67e74705SXin Li 
209*67e74705SXin Li   // Generate the report.
210*67e74705SXin Li   auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType,
211*67e74705SXin Li       "Closing a previously closed file stream", ErrNode);
212*67e74705SXin Li   R->addRange(Call.getSourceRange());
213*67e74705SXin Li   R->markInteresting(FileDescSym);
214*67e74705SXin Li   C.emitReport(std::move(R));
215*67e74705SXin Li }
216*67e74705SXin Li 
reportLeaks(ArrayRef<SymbolRef> LeakedStreams,CheckerContext & C,ExplodedNode * ErrNode) const217*67e74705SXin Li void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
218*67e74705SXin Li                                       CheckerContext &C,
219*67e74705SXin Li                                       ExplodedNode *ErrNode) const {
220*67e74705SXin Li   // Attach bug reports to the leak node.
221*67e74705SXin Li   // TODO: Identify the leaked file descriptor.
222*67e74705SXin Li   for (SymbolRef LeakedStream : LeakedStreams) {
223*67e74705SXin Li     auto R = llvm::make_unique<BugReport>(*LeakBugType,
224*67e74705SXin Li         "Opened file is never closed; potential resource leak", ErrNode);
225*67e74705SXin Li     R->markInteresting(LeakedStream);
226*67e74705SXin Li     C.emitReport(std::move(R));
227*67e74705SXin Li   }
228*67e74705SXin Li }
229*67e74705SXin Li 
guaranteedNotToCloseFile(const CallEvent & Call) const230*67e74705SXin Li bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
231*67e74705SXin Li   // If it's not in a system header, assume it might close a file.
232*67e74705SXin Li   if (!Call.isInSystemHeader())
233*67e74705SXin Li     return false;
234*67e74705SXin Li 
235*67e74705SXin Li   // Handle cases where we know a buffer's /address/ can escape.
236*67e74705SXin Li   if (Call.argumentsMayEscape())
237*67e74705SXin Li     return false;
238*67e74705SXin Li 
239*67e74705SXin Li   // Note, even though fclose closes the file, we do not list it here
240*67e74705SXin Li   // since the checker is modeling the call.
241*67e74705SXin Li 
242*67e74705SXin Li   return true;
243*67e74705SXin Li }
244*67e74705SXin Li 
245*67e74705SXin Li // If the pointer we are tracking escaped, do not track the symbol as
246*67e74705SXin Li // we cannot reason about it anymore.
247*67e74705SXin Li ProgramStateRef
checkPointerEscape(ProgramStateRef State,const InvalidatedSymbols & Escaped,const CallEvent * Call,PointerEscapeKind Kind) const248*67e74705SXin Li SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
249*67e74705SXin Li                                         const InvalidatedSymbols &Escaped,
250*67e74705SXin Li                                         const CallEvent *Call,
251*67e74705SXin Li                                         PointerEscapeKind Kind) const {
252*67e74705SXin Li   // If we know that the call cannot close a file, there is nothing to do.
253*67e74705SXin Li   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
254*67e74705SXin Li     return State;
255*67e74705SXin Li   }
256*67e74705SXin Li 
257*67e74705SXin Li   for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
258*67e74705SXin Li                                           E = Escaped.end();
259*67e74705SXin Li                                           I != E; ++I) {
260*67e74705SXin Li     SymbolRef Sym = *I;
261*67e74705SXin Li 
262*67e74705SXin Li     // The symbol escaped. Optimistically, assume that the corresponding file
263*67e74705SXin Li     // handle will be closed somewhere else.
264*67e74705SXin Li     State = State->remove<StreamMap>(Sym);
265*67e74705SXin Li   }
266*67e74705SXin Li   return State;
267*67e74705SXin Li }
268*67e74705SXin Li 
registerSimpleStreamChecker(CheckerManager & mgr)269*67e74705SXin Li void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
270*67e74705SXin Li   mgr.registerChecker<SimpleStreamChecker>();
271*67e74705SXin Li }
272