clang API Documentation

SimpleStreamChecker.cpp
Go to the documentation of this file.
00001 //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
00002 //
00003 //                     The LLVM Compiler Infrastructure
00004 //
00005 // This file is distributed under the University of Illinois Open Source
00006 // License. See LICENSE.TXT for details.
00007 //
00008 //===----------------------------------------------------------------------===//
00009 //
00010 // Defines a checker for proper use of fopen/fclose APIs.
00011 //   - If a file has been closed with fclose, it should not be accessed again.
00012 //   Accessing a closed file results in undefined behavior.
00013 //   - If a file was opened with fopen, it must be closed with fclose before
00014 //   the execution ends. Failing to do so results in a resource leak.
00015 //
00016 //===----------------------------------------------------------------------===//
00017 
00018 #include "ClangSACheckers.h"
00019 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
00020 #include "clang/StaticAnalyzer/Core/Checker.h"
00021 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
00022 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
00023 
00024 using namespace clang;
00025 using namespace ento;
00026 
00027 namespace {
00028 typedef SmallVector<SymbolRef, 2> SymbolVector;
00029 
00030 struct StreamState {
00031 private:
00032   enum Kind { Opened, Closed } K;
00033   StreamState(Kind InK) : K(InK) { }
00034 
00035 public:
00036   bool isOpened() const { return K == Opened; }
00037   bool isClosed() const { return K == Closed; }
00038 
00039   static StreamState getOpened() { return StreamState(Opened); }
00040   static StreamState getClosed() { return StreamState(Closed); }
00041 
00042   bool operator==(const StreamState &X) const {
00043     return K == X.K;
00044   }
00045   void Profile(llvm::FoldingSetNodeID &ID) const {
00046     ID.AddInteger(K);
00047   }
00048 };
00049 
00050 class SimpleStreamChecker : public Checker<check::PostCall,
00051                                            check::PreCall,
00052                                            check::DeadSymbols,
00053                                            check::PointerEscape> {
00054 
00055   mutable IdentifierInfo *IIfopen, *IIfclose;
00056 
00057   std::unique_ptr<BugType> DoubleCloseBugType;
00058   std::unique_ptr<BugType> LeakBugType;
00059 
00060   void initIdentifierInfo(ASTContext &Ctx) const;
00061 
00062   void reportDoubleClose(SymbolRef FileDescSym,
00063                          const CallEvent &Call,
00064                          CheckerContext &C) const;
00065 
00066   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
00067                    ExplodedNode *ErrNode) const;
00068 
00069   bool guaranteedNotToCloseFile(const CallEvent &Call) const;
00070 
00071 public:
00072   SimpleStreamChecker();
00073 
00074   /// Process fopen.
00075   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
00076   /// Process fclose.
00077   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
00078 
00079   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
00080 
00081   /// Stop tracking addresses which escape.
00082   ProgramStateRef checkPointerEscape(ProgramStateRef State,
00083                                     const InvalidatedSymbols &Escaped,
00084                                     const CallEvent *Call,
00085                                     PointerEscapeKind Kind) const;
00086 };
00087 
00088 } // end anonymous namespace
00089 
00090 /// The state of the checker is a map from tracked stream symbols to their
00091 /// state. Let's store it in the ProgramState.
00092 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
00093 
00094 namespace {
00095 class StopTrackingCallback : public SymbolVisitor {
00096   ProgramStateRef state;
00097 public:
00098   StopTrackingCallback(ProgramStateRef st) : state(st) {}
00099   ProgramStateRef getState() const { return state; }
00100 
00101   bool VisitSymbol(SymbolRef sym) override {
00102     state = state->remove<StreamMap>(sym);
00103     return true;
00104   }
00105 };
00106 } // end anonymous namespace
00107 
00108 SimpleStreamChecker::SimpleStreamChecker()
00109     : IIfopen(nullptr), IIfclose(nullptr) {
00110   // Initialize the bug types.
00111   DoubleCloseBugType.reset(
00112       new BugType(this, "Double fclose", "Unix Stream API Error"));
00113 
00114   LeakBugType.reset(
00115       new BugType(this, "Resource Leak", "Unix Stream API Error"));
00116   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
00117   LeakBugType->setSuppressOnSink(true);
00118 }
00119 
00120 void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
00121                                         CheckerContext &C) const {
00122   initIdentifierInfo(C.getASTContext());
00123 
00124   if (!Call.isGlobalCFunction())
00125     return;
00126 
00127   if (Call.getCalleeIdentifier() != IIfopen)
00128     return;
00129 
00130   // Get the symbolic value corresponding to the file handle.
00131   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
00132   if (!FileDesc)
00133     return;
00134 
00135   // Generate the next transition (an edge in the exploded graph).
00136   ProgramStateRef State = C.getState();
00137   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
00138   C.addTransition(State);
00139 }
00140 
00141 void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
00142                                        CheckerContext &C) const {
00143   initIdentifierInfo(C.getASTContext());
00144 
00145   if (!Call.isGlobalCFunction())
00146     return;
00147 
00148   if (Call.getCalleeIdentifier() != IIfclose)
00149     return;
00150 
00151   if (Call.getNumArgs() != 1)
00152     return;
00153 
00154   // Get the symbolic value corresponding to the file handle.
00155   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
00156   if (!FileDesc)
00157     return;
00158 
00159   // Check if the stream has already been closed.
00160   ProgramStateRef State = C.getState();
00161   const StreamState *SS = State->get<StreamMap>(FileDesc);
00162   if (SS && SS->isClosed()) {
00163     reportDoubleClose(FileDesc, Call, C);
00164     return;
00165   }
00166 
00167   // Generate the next transition, in which the stream is closed.
00168   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
00169   C.addTransition(State);
00170 }
00171 
00172 static bool isLeaked(SymbolRef Sym, const StreamState &SS,
00173                      bool IsSymDead, ProgramStateRef State) {
00174   if (IsSymDead && SS.isOpened()) {
00175     // If a symbol is NULL, assume that fopen failed on this path.
00176     // A symbol should only be considered leaked if it is non-null.
00177     ConstraintManager &CMgr = State->getConstraintManager();
00178     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
00179     return !OpenFailed.isConstrainedTrue();
00180   }
00181   return false;
00182 }
00183 
00184 void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
00185                                            CheckerContext &C) const {
00186   ProgramStateRef State = C.getState();
00187   SymbolVector LeakedStreams;
00188   StreamMapTy TrackedStreams = State->get<StreamMap>();
00189   for (StreamMapTy::iterator I = TrackedStreams.begin(),
00190                              E = TrackedStreams.end(); I != E; ++I) {
00191     SymbolRef Sym = I->first;
00192     bool IsSymDead = SymReaper.isDead(Sym);
00193 
00194     // Collect leaked symbols.
00195     if (isLeaked(Sym, I->second, IsSymDead, State))
00196       LeakedStreams.push_back(Sym);
00197 
00198     // Remove the dead symbol from the streams map.
00199     if (IsSymDead)
00200       State = State->remove<StreamMap>(Sym);
00201   }
00202 
00203   ExplodedNode *N = C.addTransition(State);
00204   reportLeaks(LeakedStreams, C, N);
00205 }
00206 
00207 void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
00208                                             const CallEvent &Call,
00209                                             CheckerContext &C) const {
00210   // We reached a bug, stop exploring the path here by generating a sink.
00211   ExplodedNode *ErrNode = C.generateSink();
00212   // If we've already reached this node on another path, return.
00213   if (!ErrNode)
00214     return;
00215 
00216   // Generate the report.
00217   BugReport *R = new BugReport(*DoubleCloseBugType,
00218       "Closing a previously closed file stream", ErrNode);
00219   R->addRange(Call.getSourceRange());
00220   R->markInteresting(FileDescSym);
00221   C.emitReport(R);
00222 }
00223 
00224 void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
00225                                       CheckerContext &C,
00226                                       ExplodedNode *ErrNode) const {
00227   // Attach bug reports to the leak node.
00228   // TODO: Identify the leaked file descriptor.
00229   for (SymbolRef LeakedStream : LeakedStreams) {
00230     BugReport *R = new BugReport(*LeakBugType,
00231         "Opened file is never closed; potential resource leak", ErrNode);
00232     R->markInteresting(LeakedStream);
00233     C.emitReport(R);
00234   }
00235 }
00236 
00237 bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
00238   // If it's not in a system header, assume it might close a file.
00239   if (!Call.isInSystemHeader())
00240     return false;
00241 
00242   // Handle cases where we know a buffer's /address/ can escape.
00243   if (Call.argumentsMayEscape())
00244     return false;
00245 
00246   // Note, even though fclose closes the file, we do not list it here
00247   // since the checker is modeling the call.
00248 
00249   return true;
00250 }
00251 
00252 // If the pointer we are tracking escaped, do not track the symbol as
00253 // we cannot reason about it anymore.
00254 ProgramStateRef
00255 SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
00256                                         const InvalidatedSymbols &Escaped,
00257                                         const CallEvent *Call,
00258                                         PointerEscapeKind Kind) const {
00259   // If we know that the call cannot close a file, there is nothing to do.
00260   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
00261     return State;
00262   }
00263 
00264   for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
00265                                           E = Escaped.end();
00266                                           I != E; ++I) {
00267     SymbolRef Sym = *I;
00268 
00269     // The symbol escaped. Optimistically, assume that the corresponding file
00270     // handle will be closed somewhere else.
00271     State = State->remove<StreamMap>(Sym);
00272   }
00273   return State;
00274 }
00275 
00276 void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const {
00277   if (IIfopen)
00278     return;
00279   IIfopen = &Ctx.Idents.get("fopen");
00280   IIfclose = &Ctx.Idents.get("fclose");
00281 }
00282 
00283 void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
00284   mgr.registerChecker<SimpleStreamChecker>();
00285 }