Home | History | Annotate | Download | only in Checkers
      1 //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This file defines checkers that model and check stream handling functions.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 
     14 #include "ClangSACheckers.h"
     15 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
     16 #include "clang/StaticAnalyzer/Core/Checker.h"
     17 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
     18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
     19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
     20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
     21 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
     22 #include "llvm/ADT/ImmutableMap.h"
     23 
     24 using namespace clang;
     25 using namespace ento;
     26 
     27 namespace {
     28 
     29 struct StreamState {
     30   enum Kind { Opened, Closed, OpenFailed, Escaped } K;
     31   const Stmt *S;
     32 
     33   StreamState(Kind k, const Stmt *s) : K(k), S(s) {}
     34 
     35   bool isOpened() const { return K == Opened; }
     36   bool isClosed() const { return K == Closed; }
     37   //bool isOpenFailed() const { return K == OpenFailed; }
     38   //bool isEscaped() const { return K == Escaped; }
     39 
     40   bool operator==(const StreamState &X) const {
     41     return K == X.K && S == X.S;
     42   }
     43 
     44   static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); }
     45   static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); }
     46   static StreamState getOpenFailed(const Stmt *s) {
     47     return StreamState(OpenFailed, s);
     48   }
     49   static StreamState getEscaped(const Stmt *s) {
     50     return StreamState(Escaped, s);
     51   }
     52 
     53   void Profile(llvm::FoldingSetNodeID &ID) const {
     54     ID.AddInteger(K);
     55     ID.AddPointer(S);
     56   }
     57 };
     58 
     59 class StreamChecker : public Checker<eval::Call,
     60                                      check::DeadSymbols > {
     61   mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread,
     62                  *II_fwrite,
     63                  *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
     64                  *II_clearerr, *II_feof, *II_ferror, *II_fileno;
     65   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
     66       BT_doubleclose, BT_ResourceLeak;
     67 
     68 public:
     69   StreamChecker()
     70     : II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr),
     71       II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr),
     72       II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr),
     73       II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr),
     74       II_ferror(nullptr), II_fileno(nullptr) {}
     75 
     76   bool evalCall(const CallExpr *CE, CheckerContext &C) const;
     77   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
     78 
     79 private:
     80   void Fopen(CheckerContext &C, const CallExpr *CE) const;
     81   void Tmpfile(CheckerContext &C, const CallExpr *CE) const;
     82   void Fclose(CheckerContext &C, const CallExpr *CE) const;
     83   void Fread(CheckerContext &C, const CallExpr *CE) const;
     84   void Fwrite(CheckerContext &C, const CallExpr *CE) const;
     85   void Fseek(CheckerContext &C, const CallExpr *CE) const;
     86   void Ftell(CheckerContext &C, const CallExpr *CE) const;
     87   void Rewind(CheckerContext &C, const CallExpr *CE) const;
     88   void Fgetpos(CheckerContext &C, const CallExpr *CE) const;
     89   void Fsetpos(CheckerContext &C, const CallExpr *CE) const;
     90   void Clearerr(CheckerContext &C, const CallExpr *CE) const;
     91   void Feof(CheckerContext &C, const CallExpr *CE) const;
     92   void Ferror(CheckerContext &C, const CallExpr *CE) const;
     93   void Fileno(CheckerContext &C, const CallExpr *CE) const;
     94 
     95   void OpenFileAux(CheckerContext &C, const CallExpr *CE) const;
     96 
     97   ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state,
     98                                  CheckerContext &C) const;
     99   ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state,
    100                                  CheckerContext &C) const;
    101 };
    102 
    103 } // end anonymous namespace
    104 
    105 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
    106 
    107 
    108 bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
    109   const FunctionDecl *FD = C.getCalleeDecl(CE);
    110   if (!FD || FD->getKind() != Decl::Function)
    111     return false;
    112 
    113   ASTContext &Ctx = C.getASTContext();
    114   if (!II_fopen)
    115     II_fopen = &Ctx.Idents.get("fopen");
    116   if (!II_tmpfile)
    117     II_tmpfile = &Ctx.Idents.get("tmpfile");
    118   if (!II_fclose)
    119     II_fclose = &Ctx.Idents.get("fclose");
    120   if (!II_fread)
    121     II_fread = &Ctx.Idents.get("fread");
    122   if (!II_fwrite)
    123     II_fwrite = &Ctx.Idents.get("fwrite");
    124   if (!II_fseek)
    125     II_fseek = &Ctx.Idents.get("fseek");
    126   if (!II_ftell)
    127     II_ftell = &Ctx.Idents.get("ftell");
    128   if (!II_rewind)
    129     II_rewind = &Ctx.Idents.get("rewind");
    130   if (!II_fgetpos)
    131     II_fgetpos = &Ctx.Idents.get("fgetpos");
    132   if (!II_fsetpos)
    133     II_fsetpos = &Ctx.Idents.get("fsetpos");
    134   if (!II_clearerr)
    135     II_clearerr = &Ctx.Idents.get("clearerr");
    136   if (!II_feof)
    137     II_feof = &Ctx.Idents.get("feof");
    138   if (!II_ferror)
    139     II_ferror = &Ctx.Idents.get("ferror");
    140   if (!II_fileno)
    141     II_fileno = &Ctx.Idents.get("fileno");
    142 
    143   if (FD->getIdentifier() == II_fopen) {
    144     Fopen(C, CE);
    145     return true;
    146   }
    147   if (FD->getIdentifier() == II_tmpfile) {
    148     Tmpfile(C, CE);
    149     return true;
    150   }
    151   if (FD->getIdentifier() == II_fclose) {
    152     Fclose(C, CE);
    153     return true;
    154   }
    155   if (FD->getIdentifier() == II_fread) {
    156     Fread(C, CE);
    157     return true;
    158   }
    159   if (FD->getIdentifier() == II_fwrite) {
    160     Fwrite(C, CE);
    161     return true;
    162   }
    163   if (FD->getIdentifier() == II_fseek) {
    164     Fseek(C, CE);
    165     return true;
    166   }
    167   if (FD->getIdentifier() == II_ftell) {
    168     Ftell(C, CE);
    169     return true;
    170   }
    171   if (FD->getIdentifier() == II_rewind) {
    172     Rewind(C, CE);
    173     return true;
    174   }
    175   if (FD->getIdentifier() == II_fgetpos) {
    176     Fgetpos(C, CE);
    177     return true;
    178   }
    179   if (FD->getIdentifier() == II_fsetpos) {
    180     Fsetpos(C, CE);
    181     return true;
    182   }
    183   if (FD->getIdentifier() == II_clearerr) {
    184     Clearerr(C, CE);
    185     return true;
    186   }
    187   if (FD->getIdentifier() == II_feof) {
    188     Feof(C, CE);
    189     return true;
    190   }
    191   if (FD->getIdentifier() == II_ferror) {
    192     Ferror(C, CE);
    193     return true;
    194   }
    195   if (FD->getIdentifier() == II_fileno) {
    196     Fileno(C, CE);
    197     return true;
    198   }
    199 
    200   return false;
    201 }
    202 
    203 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const {
    204   OpenFileAux(C, CE);
    205 }
    206 
    207 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const {
    208   OpenFileAux(C, CE);
    209 }
    210 
    211 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const {
    212   ProgramStateRef state = C.getState();
    213   SValBuilder &svalBuilder = C.getSValBuilder();
    214   const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
    215   DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx,
    216                                                     C.blockCount())
    217       .castAs<DefinedSVal>();
    218   state = state->BindExpr(CE, C.getLocationContext(), RetVal);
    219 
    220   ConstraintManager &CM = C.getConstraintManager();
    221   // Bifurcate the state into two: one with a valid FILE* pointer, the other
    222   // with a NULL.
    223   ProgramStateRef stateNotNull, stateNull;
    224   std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
    225 
    226   if (SymbolRef Sym = RetVal.getAsSymbol()) {
    227     // if RetVal is not NULL, set the symbol's state to Opened.
    228     stateNotNull =
    229       stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE));
    230     stateNull =
    231       stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE));
    232 
    233     C.addTransition(stateNotNull);
    234     C.addTransition(stateNull);
    235   }
    236 }
    237 
    238 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const {
    239   ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C);
    240   if (state)
    241     C.addTransition(state);
    242 }
    243 
    244 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const {
    245   ProgramStateRef state = C.getState();
    246   if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()),
    247                        state, C))
    248     return;
    249 }
    250 
    251 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const {
    252   ProgramStateRef state = C.getState();
    253   if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()),
    254                        state, C))
    255     return;
    256 }
    257 
    258 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const {
    259   ProgramStateRef state = C.getState();
    260   if (!(state = CheckNullStream(state->getSVal(CE->getArg(0),
    261                                                C.getLocationContext()), state, C)))
    262     return;
    263   // Check the legality of the 'whence' argument of 'fseek'.
    264   SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext());
    265   Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>();
    266 
    267   if (!CI)
    268     return;
    269 
    270   int64_t x = CI->getValue().getSExtValue();
    271   if (x >= 0 && x <= 2)
    272     return;
    273 
    274   if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
    275     if (!BT_illegalwhence)
    276       BT_illegalwhence.reset(
    277           new BuiltinBug(this, "Illegal whence argument",
    278                          "The whence argument to fseek() should be "
    279                          "SEEK_SET, SEEK_END, or SEEK_CUR."));
    280     C.emitReport(llvm::make_unique<BugReport>(
    281         *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
    282   }
    283 }
    284 
    285 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const {
    286   ProgramStateRef state = C.getState();
    287   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    288                        state, C))
    289     return;
    290 }
    291 
    292 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const {
    293   ProgramStateRef state = C.getState();
    294   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    295                        state, C))
    296     return;
    297 }
    298 
    299 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const {
    300   ProgramStateRef state = C.getState();
    301   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    302                        state, C))
    303     return;
    304 }
    305 
    306 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const {
    307   ProgramStateRef state = C.getState();
    308   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    309                        state, C))
    310     return;
    311 }
    312 
    313 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const {
    314   ProgramStateRef state = C.getState();
    315   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    316                        state, C))
    317     return;
    318 }
    319 
    320 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const {
    321   ProgramStateRef state = C.getState();
    322   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    323                        state, C))
    324     return;
    325 }
    326 
    327 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const {
    328   ProgramStateRef state = C.getState();
    329   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    330                        state, C))
    331     return;
    332 }
    333 
    334 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const {
    335   ProgramStateRef state = C.getState();
    336   if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()),
    337                        state, C))
    338     return;
    339 }
    340 
    341 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state,
    342                                     CheckerContext &C) const {
    343   Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
    344   if (!DV)
    345     return nullptr;
    346 
    347   ConstraintManager &CM = C.getConstraintManager();
    348   ProgramStateRef stateNotNull, stateNull;
    349   std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
    350 
    351   if (!stateNotNull && stateNull) {
    352     if (ExplodedNode *N = C.generateErrorNode(stateNull)) {
    353       if (!BT_nullfp)
    354         BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
    355                                        "Stream pointer might be NULL."));
    356       C.emitReport(llvm::make_unique<BugReport>(
    357           *BT_nullfp, BT_nullfp->getDescription(), N));
    358     }
    359     return nullptr;
    360   }
    361   return stateNotNull;
    362 }
    363 
    364 ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
    365                                                ProgramStateRef state,
    366                                                CheckerContext &C) const {
    367   SymbolRef Sym =
    368     state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol();
    369   if (!Sym)
    370     return state;
    371 
    372   const StreamState *SS = state->get<StreamMap>(Sym);
    373 
    374   // If the file stream is not tracked, return.
    375   if (!SS)
    376     return state;
    377 
    378   // Check: Double close a File Descriptor could cause undefined behaviour.
    379   // Conforming to man-pages
    380   if (SS->isClosed()) {
    381     ExplodedNode *N = C.generateErrorNode();
    382     if (N) {
    383       if (!BT_doubleclose)
    384         BT_doubleclose.reset(new BuiltinBug(
    385             this, "Double fclose", "Try to close a file Descriptor already"
    386                                    " closed. Cause undefined behaviour."));
    387       C.emitReport(llvm::make_unique<BugReport>(
    388           *BT_doubleclose, BT_doubleclose->getDescription(), N));
    389     }
    390     return nullptr;
    391   }
    392 
    393   // Close the File Descriptor.
    394   return state->set<StreamMap>(Sym, StreamState::getClosed(CE));
    395 }
    396 
    397 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
    398                                      CheckerContext &C) const {
    399   // TODO: Clean up the state.
    400   for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
    401          E = SymReaper.dead_end(); I != E; ++I) {
    402     SymbolRef Sym = *I;
    403     ProgramStateRef state = C.getState();
    404     const StreamState *SS = state->get<StreamMap>(Sym);
    405     if (!SS)
    406       continue;
    407 
    408     if (SS->isOpened()) {
    409       ExplodedNode *N = C.generateErrorNode();
    410       if (N) {
    411         if (!BT_ResourceLeak)
    412           BT_ResourceLeak.reset(new BuiltinBug(
    413               this, "Resource Leak",
    414               "Opened File never closed. Potential Resource leak."));
    415         C.emitReport(llvm::make_unique<BugReport>(
    416             *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
    417       }
    418     }
    419   }
    420 }
    421 
    422 void ento::registerStreamChecker(CheckerManager &mgr) {
    423   mgr.registerChecker<StreamChecker>();
    424 }
    425