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