1243791Sdim//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
2243791Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6243791Sdim//
7243791Sdim//===----------------------------------------------------------------------===//
8243791Sdim//
9243791Sdim// Defines a checker for proper use of fopen/fclose APIs.
10243791Sdim//   - If a file has been closed with fclose, it should not be accessed again.
11243791Sdim//   Accessing a closed file results in undefined behavior.
12243791Sdim//   - If a file was opened with fopen, it must be closed with fclose before
13243791Sdim//   the execution ends. Failing to do so results in a resource leak.
14243791Sdim//
15243791Sdim//===----------------------------------------------------------------------===//
16243791Sdim
17344779Sdim#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h"
20243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
21243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22309124Sdim#include <utility>
23243791Sdim
24243791Sdimusing namespace clang;
25243791Sdimusing namespace ento;
26243791Sdim
27243791Sdimnamespace {
28249423Sdimtypedef SmallVector<SymbolRef, 2> SymbolVector;
29243791Sdim
30243791Sdimstruct StreamState {
31243791Sdimprivate:
32243791Sdim  enum Kind { Opened, Closed } K;
33243791Sdim  StreamState(Kind InK) : K(InK) { }
34243791Sdim
35243791Sdimpublic:
36243791Sdim  bool isOpened() const { return K == Opened; }
37243791Sdim  bool isClosed() const { return K == Closed; }
38243791Sdim
39243791Sdim  static StreamState getOpened() { return StreamState(Opened); }
40243791Sdim  static StreamState getClosed() { return StreamState(Closed); }
41243791Sdim
42243791Sdim  bool operator==(const StreamState &X) const {
43243791Sdim    return K == X.K;
44243791Sdim  }
45243791Sdim  void Profile(llvm::FoldingSetNodeID &ID) const {
46243791Sdim    ID.AddInteger(K);
47243791Sdim  }
48243791Sdim};
49243791Sdim
50243791Sdimclass SimpleStreamChecker : public Checker<check::PostCall,
51243791Sdim                                           check::PreCall,
52243791Sdim                                           check::DeadSymbols,
53249423Sdim                                           check::PointerEscape> {
54309124Sdim  CallDescription OpenFn, CloseFn;
55243791Sdim
56276479Sdim  std::unique_ptr<BugType> DoubleCloseBugType;
57276479Sdim  std::unique_ptr<BugType> LeakBugType;
58243791Sdim
59243791Sdim  void reportDoubleClose(SymbolRef FileDescSym,
60243791Sdim                         const CallEvent &Call,
61243791Sdim                         CheckerContext &C) const;
62243791Sdim
63280031Sdim  void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
64243791Sdim                   ExplodedNode *ErrNode) const;
65243791Sdim
66243791Sdim  bool guaranteedNotToCloseFile(const CallEvent &Call) const;
67243791Sdim
68243791Sdimpublic:
69243791Sdim  SimpleStreamChecker();
70243791Sdim
71243791Sdim  /// Process fopen.
72243791Sdim  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
73243791Sdim  /// Process fclose.
74243791Sdim  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
75243791Sdim
76243791Sdim  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
77243791Sdim
78249423Sdim  /// Stop tracking addresses which escape.
79249423Sdim  ProgramStateRef checkPointerEscape(ProgramStateRef State,
80249423Sdim                                    const InvalidatedSymbols &Escaped,
81249423Sdim                                    const CallEvent *Call,
82249423Sdim                                    PointerEscapeKind Kind) const;
83243791Sdim};
84243791Sdim
85243791Sdim} // end anonymous namespace
86243791Sdim
87243791Sdim/// The state of the checker is a map from tracked stream symbols to their
88243791Sdim/// state. Let's store it in the ProgramState.
89243791SdimREGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
90243791Sdim
91243791Sdimnamespace {
92296417Sdimclass StopTrackingCallback final : public SymbolVisitor {
93243791Sdim  ProgramStateRef state;
94243791Sdimpublic:
95309124Sdim  StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
96243791Sdim  ProgramStateRef getState() const { return state; }
97243791Sdim
98276479Sdim  bool VisitSymbol(SymbolRef sym) override {
99243791Sdim    state = state->remove<StreamMap>(sym);
100243791Sdim    return true;
101243791Sdim  }
102243791Sdim};
103243791Sdim} // end anonymous namespace
104243791Sdim
105276479SdimSimpleStreamChecker::SimpleStreamChecker()
106309124Sdim    : OpenFn("fopen"), CloseFn("fclose", 1) {
107243791Sdim  // Initialize the bug types.
108276479Sdim  DoubleCloseBugType.reset(
109276479Sdim      new BugType(this, "Double fclose", "Unix Stream API Error"));
110243791Sdim
111353358Sdim  // Sinks are higher importance bugs as well as calls to assert() or exit(0).
112276479Sdim  LeakBugType.reset(
113353358Sdim      new BugType(this, "Resource Leak", "Unix Stream API Error",
114353358Sdim                  /*SuppressOnSink=*/true));
115243791Sdim}
116243791Sdim
117243791Sdimvoid SimpleStreamChecker::checkPostCall(const CallEvent &Call,
118243791Sdim                                        CheckerContext &C) const {
119243791Sdim  if (!Call.isGlobalCFunction())
120243791Sdim    return;
121243791Sdim
122309124Sdim  if (!Call.isCalled(OpenFn))
123243791Sdim    return;
124243791Sdim
125243791Sdim  // Get the symbolic value corresponding to the file handle.
126243791Sdim  SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
127243791Sdim  if (!FileDesc)
128243791Sdim    return;
129243791Sdim
130243791Sdim  // Generate the next transition (an edge in the exploded graph).
131243791Sdim  ProgramStateRef State = C.getState();
132243791Sdim  State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
133243791Sdim  C.addTransition(State);
134243791Sdim}
135243791Sdim
136243791Sdimvoid SimpleStreamChecker::checkPreCall(const CallEvent &Call,
137243791Sdim                                       CheckerContext &C) const {
138243791Sdim  if (!Call.isGlobalCFunction())
139243791Sdim    return;
140243791Sdim
141309124Sdim  if (!Call.isCalled(CloseFn))
142243791Sdim    return;
143243791Sdim
144243791Sdim  // Get the symbolic value corresponding to the file handle.
145243791Sdim  SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
146243791Sdim  if (!FileDesc)
147243791Sdim    return;
148243791Sdim
149243791Sdim  // Check if the stream has already been closed.
150243791Sdim  ProgramStateRef State = C.getState();
151243791Sdim  const StreamState *SS = State->get<StreamMap>(FileDesc);
152243791Sdim  if (SS && SS->isClosed()) {
153243791Sdim    reportDoubleClose(FileDesc, Call, C);
154243791Sdim    return;
155243791Sdim  }
156243791Sdim
157243791Sdim  // Generate the next transition, in which the stream is closed.
158243791Sdim  State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
159243791Sdim  C.addTransition(State);
160243791Sdim}
161243791Sdim
162243791Sdimstatic bool isLeaked(SymbolRef Sym, const StreamState &SS,
163243791Sdim                     bool IsSymDead, ProgramStateRef State) {
164243791Sdim  if (IsSymDead && SS.isOpened()) {
165243791Sdim    // If a symbol is NULL, assume that fopen failed on this path.
166243791Sdim    // A symbol should only be considered leaked if it is non-null.
167243791Sdim    ConstraintManager &CMgr = State->getConstraintManager();
168243791Sdim    ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
169243791Sdim    return !OpenFailed.isConstrainedTrue();
170243791Sdim  }
171243791Sdim  return false;
172243791Sdim}
173243791Sdim
174243791Sdimvoid SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
175243791Sdim                                           CheckerContext &C) const {
176243791Sdim  ProgramStateRef State = C.getState();
177243791Sdim  SymbolVector LeakedStreams;
178243791Sdim  StreamMapTy TrackedStreams = State->get<StreamMap>();
179243791Sdim  for (StreamMapTy::iterator I = TrackedStreams.begin(),
180243791Sdim                             E = TrackedStreams.end(); I != E; ++I) {
181243791Sdim    SymbolRef Sym = I->first;
182243791Sdim    bool IsSymDead = SymReaper.isDead(Sym);
183243791Sdim
184243791Sdim    // Collect leaked symbols.
185243791Sdim    if (isLeaked(Sym, I->second, IsSymDead, State))
186243791Sdim      LeakedStreams.push_back(Sym);
187243791Sdim
188243791Sdim    // Remove the dead symbol from the streams map.
189243791Sdim    if (IsSymDead)
190243791Sdim      State = State->remove<StreamMap>(Sym);
191243791Sdim  }
192243791Sdim
193296417Sdim  ExplodedNode *N = C.generateNonFatalErrorNode(State);
194296417Sdim  if (!N)
195296417Sdim    return;
196243791Sdim  reportLeaks(LeakedStreams, C, N);
197243791Sdim}
198243791Sdim
199243791Sdimvoid SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
200243791Sdim                                            const CallEvent &Call,
201243791Sdim                                            CheckerContext &C) const {
202243791Sdim  // We reached a bug, stop exploring the path here by generating a sink.
203296417Sdim  ExplodedNode *ErrNode = C.generateErrorNode();
204243791Sdim  // If we've already reached this node on another path, return.
205243791Sdim  if (!ErrNode)
206243791Sdim    return;
207243791Sdim
208243791Sdim  // Generate the report.
209360784Sdim  auto R = std::make_unique<PathSensitiveBugReport>(
210360784Sdim      *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
211243791Sdim  R->addRange(Call.getSourceRange());
212243791Sdim  R->markInteresting(FileDescSym);
213288943Sdim  C.emitReport(std::move(R));
214243791Sdim}
215243791Sdim
216280031Sdimvoid SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
217280031Sdim                                      CheckerContext &C,
218280031Sdim                                      ExplodedNode *ErrNode) const {
219243791Sdim  // Attach bug reports to the leak node.
220243791Sdim  // TODO: Identify the leaked file descriptor.
221280031Sdim  for (SymbolRef LeakedStream : LeakedStreams) {
222360784Sdim    auto R = std::make_unique<PathSensitiveBugReport>(
223360784Sdim        *LeakBugType, "Opened file is never closed; potential resource leak",
224360784Sdim        ErrNode);
225280031Sdim    R->markInteresting(LeakedStream);
226288943Sdim    C.emitReport(std::move(R));
227243791Sdim  }
228243791Sdim}
229243791Sdim
230243791Sdimbool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
231243791Sdim  // If it's not in a system header, assume it might close a file.
232243791Sdim  if (!Call.isInSystemHeader())
233243791Sdim    return false;
234243791Sdim
235243791Sdim  // Handle cases where we know a buffer's /address/ can escape.
236243791Sdim  if (Call.argumentsMayEscape())
237243791Sdim    return false;
238243791Sdim
239243791Sdim  // Note, even though fclose closes the file, we do not list it here
240243791Sdim  // since the checker is modeling the call.
241243791Sdim
242243791Sdim  return true;
243243791Sdim}
244243791Sdim
245249423Sdim// If the pointer we are tracking escaped, do not track the symbol as
246243791Sdim// we cannot reason about it anymore.
247243791SdimProgramStateRef
248249423SdimSimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
249249423Sdim                                        const InvalidatedSymbols &Escaped,
250249423Sdim                                        const CallEvent *Call,
251249423Sdim                                        PointerEscapeKind Kind) const {
252249423Sdim  // If we know that the call cannot close a file, there is nothing to do.
253261991Sdim  if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
254243791Sdim    return State;
255243791Sdim  }
256243791Sdim
257249423Sdim  for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
258249423Sdim                                          E = Escaped.end();
259249423Sdim                                          I != E; ++I) {
260249423Sdim    SymbolRef Sym = *I;
261249423Sdim
262243791Sdim    // The symbol escaped. Optimistically, assume that the corresponding file
263243791Sdim    // handle will be closed somewhere else.
264249423Sdim    State = State->remove<StreamMap>(Sym);
265243791Sdim  }
266243791Sdim  return State;
267243791Sdim}
268243791Sdim
269243791Sdimvoid ento::registerSimpleStreamChecker(CheckerManager &mgr) {
270243791Sdim  mgr.registerChecker<SimpleStreamChecker>();
271243791Sdim}
272353358Sdim
273353358Sdim// This checker should be enabled regardless of how language options are set.
274353358Sdimbool ento::shouldRegisterSimpleStreamChecker(const LangOptions &LO) {
275353358Sdim  return true;
276353358Sdim}
277