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