1243791Sdim//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
2243791Sdim//
3243791Sdim//                     The LLVM Compiler Infrastructure
4243791Sdim//
5243791Sdim// This file is distributed under the University of Illinois Open Source
6243791Sdim// License. See LICENSE.TXT for details.
7243791Sdim//
8243791Sdim//===----------------------------------------------------------------------===//
9243791Sdim//
10243791Sdim// Defines a checker for proper use of fopen/fclose APIs.
11243791Sdim//   - If a file has been closed with fclose, it should not be accessed again.
12243791Sdim//   Accessing a closed file results in undefined behavior.
13243791Sdim//   - If a file was opened with fopen, it must be closed with fclose before
14243791Sdim//   the execution ends. Failing to do so results in a resource leak.
15243791Sdim//
16243791Sdim//===----------------------------------------------------------------------===//
17243791Sdim
18243791Sdim#include "ClangSACheckers.h"
19252723Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
20243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h"
21243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23243791Sdim
24243791Sdimusing namespace clang;
25243791Sdimusing namespace ento;
26243791Sdim
27243791Sdimnamespace {
28252723Sdimtypedef 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,
53252723Sdim                                           check::PointerEscape> {
54243791Sdim
55243791Sdim  mutable IdentifierInfo *IIfopen, *IIfclose;
56243791Sdim
57243791Sdim  OwningPtr<BugType> DoubleCloseBugType;
58243791Sdim  OwningPtr<BugType> LeakBugType;
59243791Sdim
60243791Sdim  void initIdentifierInfo(ASTContext &Ctx) const;
61243791Sdim
62243791Sdim  void reportDoubleClose(SymbolRef FileDescSym,
63243791Sdim                         const CallEvent &Call,
64243791Sdim                         CheckerContext &C) const;
65243791Sdim
66243791Sdim  void reportLeaks(SymbolVector LeakedStreams,
67243791Sdim                   CheckerContext &C,
68243791Sdim                   ExplodedNode *ErrNode) const;
69243791Sdim
70243791Sdim  bool guaranteedNotToCloseFile(const CallEvent &Call) const;
71243791Sdim
72243791Sdimpublic:
73243791Sdim  SimpleStreamChecker();
74243791Sdim
75243791Sdim  /// Process fopen.
76243791Sdim  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
77243791Sdim  /// Process fclose.
78243791Sdim  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
79243791Sdim
80243791Sdim  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
81243791Sdim
82252723Sdim  /// Stop tracking addresses which escape.
83252723Sdim  ProgramStateRef checkPointerEscape(ProgramStateRef State,
84252723Sdim                                    const InvalidatedSymbols &Escaped,
85252723Sdim                                    const CallEvent *Call,
86252723Sdim                                    PointerEscapeKind Kind) const;
87243791Sdim};
88243791Sdim
89243791Sdim} // end anonymous namespace
90243791Sdim
91243791Sdim/// The state of the checker is a map from tracked stream symbols to their
92243791Sdim/// state. Let's store it in the ProgramState.
93243791SdimREGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
94243791Sdim
95243791Sdimnamespace {
96243791Sdimclass StopTrackingCallback : public SymbolVisitor {
97243791Sdim  ProgramStateRef state;
98243791Sdimpublic:
99243791Sdim  StopTrackingCallback(ProgramStateRef st) : state(st) {}
100243791Sdim  ProgramStateRef getState() const { return state; }
101243791Sdim
102243791Sdim  bool VisitSymbol(SymbolRef sym) {
103243791Sdim    state = state->remove<StreamMap>(sym);
104243791Sdim    return true;
105243791Sdim  }
106243791Sdim};
107243791Sdim} // end anonymous namespace
108243791Sdim
109243791Sdim
110243791SdimSimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) {
111243791Sdim  // Initialize the bug types.
112243791Sdim  DoubleCloseBugType.reset(new BugType("Double fclose",
113243791Sdim                                       "Unix Stream API Error"));
114243791Sdim
115243791Sdim  LeakBugType.reset(new BugType("Resource Leak",
116243791Sdim                                "Unix Stream API Error"));
117243791Sdim  // Sinks are higher importance bugs as well as calls to assert() or exit(0).
118243791Sdim  LeakBugType->setSuppressOnSink(true);
119243791Sdim}
120243791Sdim
121243791Sdimvoid SimpleStreamChecker::checkPostCall(const CallEvent &Call,
122243791Sdim                                        CheckerContext &C) const {
123243791Sdim  initIdentifierInfo(C.getASTContext());
124243791Sdim
125243791Sdim  if (!Call.isGlobalCFunction())
126243791Sdim    return;
127243791Sdim
128243791Sdim  if (Call.getCalleeIdentifier() != IIfopen)
129243791Sdim    return;
130243791Sdim
131243791Sdim  // Get the symbolic value corresponding to the file handle.
132243791Sdim  SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
133243791Sdim  if (!FileDesc)
134243791Sdim    return;
135243791Sdim
136243791Sdim  // Generate the next transition (an edge in the exploded graph).
137243791Sdim  ProgramStateRef State = C.getState();
138243791Sdim  State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
139243791Sdim  C.addTransition(State);
140243791Sdim}
141243791Sdim
142243791Sdimvoid SimpleStreamChecker::checkPreCall(const CallEvent &Call,
143243791Sdim                                       CheckerContext &C) const {
144243791Sdim  initIdentifierInfo(C.getASTContext());
145243791Sdim
146243791Sdim  if (!Call.isGlobalCFunction())
147243791Sdim    return;
148243791Sdim
149243791Sdim  if (Call.getCalleeIdentifier() != IIfclose)
150243791Sdim    return;
151243791Sdim
152243791Sdim  if (Call.getNumArgs() != 1)
153243791Sdim    return;
154243791Sdim
155243791Sdim  // Get the symbolic value corresponding to the file handle.
156243791Sdim  SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
157243791Sdim  if (!FileDesc)
158243791Sdim    return;
159243791Sdim
160243791Sdim  // Check if the stream has already been closed.
161243791Sdim  ProgramStateRef State = C.getState();
162243791Sdim  const StreamState *SS = State->get<StreamMap>(FileDesc);
163243791Sdim  if (SS && SS->isClosed()) {
164243791Sdim    reportDoubleClose(FileDesc, Call, C);
165243791Sdim    return;
166243791Sdim  }
167243791Sdim
168243791Sdim  // Generate the next transition, in which the stream is closed.
169243791Sdim  State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
170243791Sdim  C.addTransition(State);
171243791Sdim}
172243791Sdim
173243791Sdimstatic bool isLeaked(SymbolRef Sym, const StreamState &SS,
174243791Sdim                     bool IsSymDead, ProgramStateRef State) {
175243791Sdim  if (IsSymDead && SS.isOpened()) {
176243791Sdim    // If a symbol is NULL, assume that fopen failed on this path.
177243791Sdim    // A symbol should only be considered leaked if it is non-null.
178243791Sdim    ConstraintManager &CMgr = State->getConstraintManager();
179243791Sdim    ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
180243791Sdim    return !OpenFailed.isConstrainedTrue();
181243791Sdim  }
182243791Sdim  return false;
183243791Sdim}
184243791Sdim
185243791Sdimvoid SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
186243791Sdim                                           CheckerContext &C) const {
187243791Sdim  ProgramStateRef State = C.getState();
188243791Sdim  SymbolVector LeakedStreams;
189243791Sdim  StreamMapTy TrackedStreams = State->get<StreamMap>();
190243791Sdim  for (StreamMapTy::iterator I = TrackedStreams.begin(),
191243791Sdim                             E = TrackedStreams.end(); I != E; ++I) {
192243791Sdim    SymbolRef Sym = I->first;
193243791Sdim    bool IsSymDead = SymReaper.isDead(Sym);
194243791Sdim
195243791Sdim    // Collect leaked symbols.
196243791Sdim    if (isLeaked(Sym, I->second, IsSymDead, State))
197243791Sdim      LeakedStreams.push_back(Sym);
198243791Sdim
199243791Sdim    // Remove the dead symbol from the streams map.
200243791Sdim    if (IsSymDead)
201243791Sdim      State = State->remove<StreamMap>(Sym);
202243791Sdim  }
203243791Sdim
204243791Sdim  ExplodedNode *N = C.addTransition(State);
205243791Sdim  reportLeaks(LeakedStreams, C, N);
206243791Sdim}
207243791Sdim
208243791Sdimvoid SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
209243791Sdim                                            const CallEvent &Call,
210243791Sdim                                            CheckerContext &C) const {
211243791Sdim  // We reached a bug, stop exploring the path here by generating a sink.
212243791Sdim  ExplodedNode *ErrNode = C.generateSink();
213243791Sdim  // If we've already reached this node on another path, return.
214243791Sdim  if (!ErrNode)
215243791Sdim    return;
216243791Sdim
217243791Sdim  // Generate the report.
218243791Sdim  BugReport *R = new BugReport(*DoubleCloseBugType,
219243791Sdim      "Closing a previously closed file stream", ErrNode);
220243791Sdim  R->addRange(Call.getSourceRange());
221243791Sdim  R->markInteresting(FileDescSym);
222243791Sdim  C.emitReport(R);
223243791Sdim}
224243791Sdim
225243791Sdimvoid SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams,
226243791Sdim                                               CheckerContext &C,
227243791Sdim                                               ExplodedNode *ErrNode) const {
228243791Sdim  // Attach bug reports to the leak node.
229243791Sdim  // TODO: Identify the leaked file descriptor.
230263509Sdim  for (SmallVectorImpl<SymbolRef>::iterator
231263509Sdim         I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) {
232243791Sdim    BugReport *R = new BugReport(*LeakBugType,
233243791Sdim        "Opened file is never closed; potential resource leak", ErrNode);
234243791Sdim    R->markInteresting(*I);
235243791Sdim    C.emitReport(R);
236243791Sdim  }
237243791Sdim}
238243791Sdim
239243791Sdimbool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
240243791Sdim  // If it's not in a system header, assume it might close a file.
241243791Sdim  if (!Call.isInSystemHeader())
242243791Sdim    return false;
243243791Sdim
244243791Sdim  // Handle cases where we know a buffer's /address/ can escape.
245243791Sdim  if (Call.argumentsMayEscape())
246243791Sdim    return false;
247243791Sdim
248243791Sdim  // Note, even though fclose closes the file, we do not list it here
249243791Sdim  // since the checker is modeling the call.
250243791Sdim
251243791Sdim  return true;
252243791Sdim}
253243791Sdim
254252723Sdim// If the pointer we are tracking escaped, do not track the symbol as
255243791Sdim// we cannot reason about it anymore.
256243791SdimProgramStateRef
257252723SdimSimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
258252723Sdim                                        const InvalidatedSymbols &Escaped,
259252723Sdim                                        const CallEvent *Call,
260252723Sdim                                        PointerEscapeKind Kind) const {
261252723Sdim  // If we know that the call cannot close a file, there is nothing to do.
262263509Sdim  if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
263243791Sdim    return State;
264243791Sdim  }
265243791Sdim
266252723Sdim  for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
267252723Sdim                                          E = Escaped.end();
268252723Sdim                                          I != E; ++I) {
269252723Sdim    SymbolRef Sym = *I;
270252723Sdim
271243791Sdim    // The symbol escaped. Optimistically, assume that the corresponding file
272243791Sdim    // handle will be closed somewhere else.
273252723Sdim    State = State->remove<StreamMap>(Sym);
274243791Sdim  }
275243791Sdim  return State;
276243791Sdim}
277243791Sdim
278243791Sdimvoid SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const {
279243791Sdim  if (IIfopen)
280243791Sdim    return;
281243791Sdim  IIfopen = &Ctx.Idents.get("fopen");
282243791Sdim  IIfclose = &Ctx.Idents.get("fclose");
283243791Sdim}
284243791Sdim
285243791Sdimvoid ento::registerSimpleStreamChecker(CheckerManager &mgr) {
286243791Sdim  mgr.registerChecker<SimpleStreamChecker>();
287243791Sdim}
288