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