diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp')
| -rw-r--r-- | contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp | 348 | 
1 files changed, 348 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp new file mode 100644 index 000000000000..ee055adf6e4d --- /dev/null +++ b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -0,0 +1,348 @@ +//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for proper use of fopen/fclose APIs. +//   - If a file has been closed with fclose, it should not be accessed again. +//   Accessing a closed file results in undefined behavior. +//   - If a file was opened with fopen, it must be closed with fclose before +//   the execution ends. Failing to do so results in a resource leak. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +typedef llvm::SmallVector<SymbolRef, 2> SymbolVector; + +struct StreamState { +private: +  enum Kind { Opened, Closed } K; +  StreamState(Kind InK) : K(InK) { } + +public: +  bool isOpened() const { return K == Opened; } +  bool isClosed() const { return K == Closed; } + +  static StreamState getOpened() { return StreamState(Opened); } +  static StreamState getClosed() { return StreamState(Closed); } + +  bool operator==(const StreamState &X) const { +    return K == X.K; +  } +  void Profile(llvm::FoldingSetNodeID &ID) const { +    ID.AddInteger(K); +  } +}; + +class SimpleStreamChecker : public Checker<check::PostCall, +                                           check::PreCall, +                                           check::DeadSymbols, +                                           check::Bind, +                                           check::RegionChanges> { + +  mutable IdentifierInfo *IIfopen, *IIfclose; + +  OwningPtr<BugType> DoubleCloseBugType; +  OwningPtr<BugType> LeakBugType; + +  void initIdentifierInfo(ASTContext &Ctx) const; + +  void reportDoubleClose(SymbolRef FileDescSym, +                         const CallEvent &Call, +                         CheckerContext &C) const; + +  void reportLeaks(SymbolVector LeakedStreams, +                   CheckerContext &C, +                   ExplodedNode *ErrNode) const; + +  bool guaranteedNotToCloseFile(const CallEvent &Call) const; + +public: +  SimpleStreamChecker(); + +  /// Process fopen. +  void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +  /// Process fclose. +  void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + +  /// Deal with symbol escape as a byproduct of a bind. +  void checkBind(SVal location, SVal val, const Stmt*S, +                 CheckerContext &C) const; + +  /// Deal with symbol escape as a byproduct of a region change. +  ProgramStateRef +  checkRegionChanges(ProgramStateRef state, +                     const StoreManager::InvalidatedSymbols *invalidated, +                     ArrayRef<const MemRegion *> ExplicitRegions, +                     ArrayRef<const MemRegion *> Regions, +                     const CallEvent *Call) const; +  bool wantsRegionChangeUpdate(ProgramStateRef state) const { +    return true; +  } +}; + +} // end anonymous namespace + +/// The state of the checker is a map from tracked stream symbols to their +/// state. Let's store it in the ProgramState. +REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) + +namespace { +class StopTrackingCallback : public SymbolVisitor { +  ProgramStateRef state; +public: +  StopTrackingCallback(ProgramStateRef st) : state(st) {} +  ProgramStateRef getState() const { return state; } + +  bool VisitSymbol(SymbolRef sym) { +    state = state->remove<StreamMap>(sym); +    return true; +  } +}; +} // end anonymous namespace + + +SimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) { +  // Initialize the bug types. +  DoubleCloseBugType.reset(new BugType("Double fclose", +                                       "Unix Stream API Error")); + +  LeakBugType.reset(new BugType("Resource Leak", +                                "Unix Stream API Error")); +  // Sinks are higher importance bugs as well as calls to assert() or exit(0). +  LeakBugType->setSuppressOnSink(true); +} + +void SimpleStreamChecker::checkPostCall(const CallEvent &Call, +                                        CheckerContext &C) const { +  initIdentifierInfo(C.getASTContext()); + +  if (!Call.isGlobalCFunction()) +    return; + +  if (Call.getCalleeIdentifier() != IIfopen) +    return; + +  // Get the symbolic value corresponding to the file handle. +  SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); +  if (!FileDesc) +    return; + +  // Generate the next transition (an edge in the exploded graph). +  ProgramStateRef State = C.getState(); +  State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); +  C.addTransition(State); +} + +void SimpleStreamChecker::checkPreCall(const CallEvent &Call, +                                       CheckerContext &C) const { +  initIdentifierInfo(C.getASTContext()); + +  if (!Call.isGlobalCFunction()) +    return; + +  if (Call.getCalleeIdentifier() != IIfclose) +    return; + +  if (Call.getNumArgs() != 1) +    return; + +  // Get the symbolic value corresponding to the file handle. +  SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); +  if (!FileDesc) +    return; + +  // Check if the stream has already been closed. +  ProgramStateRef State = C.getState(); +  const StreamState *SS = State->get<StreamMap>(FileDesc); +  if (SS && SS->isClosed()) { +    reportDoubleClose(FileDesc, Call, C); +    return; +  } + +  // Generate the next transition, in which the stream is closed. +  State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); +  C.addTransition(State); +} + +static bool isLeaked(SymbolRef Sym, const StreamState &SS, +                     bool IsSymDead, ProgramStateRef State) { +  if (IsSymDead && SS.isOpened()) { +    // If a symbol is NULL, assume that fopen failed on this path. +    // A symbol should only be considered leaked if it is non-null. +    ConstraintManager &CMgr = State->getConstraintManager(); +    ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); +    return !OpenFailed.isConstrainedTrue(); +  } +  return false; +} + +void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, +                                           CheckerContext &C) const { +  ProgramStateRef State = C.getState(); +  SymbolVector LeakedStreams; +  StreamMapTy TrackedStreams = State->get<StreamMap>(); +  for (StreamMapTy::iterator I = TrackedStreams.begin(), +                             E = TrackedStreams.end(); I != E; ++I) { +    SymbolRef Sym = I->first; +    bool IsSymDead = SymReaper.isDead(Sym); + +    // Collect leaked symbols. +    if (isLeaked(Sym, I->second, IsSymDead, State)) +      LeakedStreams.push_back(Sym); + +    // Remove the dead symbol from the streams map. +    if (IsSymDead) +      State = State->remove<StreamMap>(Sym); +  } + +  ExplodedNode *N = C.addTransition(State); +  reportLeaks(LeakedStreams, C, N); +} + +void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, +                                            const CallEvent &Call, +                                            CheckerContext &C) const { +  // We reached a bug, stop exploring the path here by generating a sink. +  ExplodedNode *ErrNode = C.generateSink(); +  // If we've already reached this node on another path, return. +  if (!ErrNode) +    return; + +  // Generate the report. +  BugReport *R = new BugReport(*DoubleCloseBugType, +      "Closing a previously closed file stream", ErrNode); +  R->addRange(Call.getSourceRange()); +  R->markInteresting(FileDescSym); +  C.emitReport(R); +} + +void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, +                                               CheckerContext &C, +                                               ExplodedNode *ErrNode) const { +  // Attach bug reports to the leak node. +  // TODO: Identify the leaked file descriptor. +  for (llvm::SmallVector<SymbolRef, 2>::iterator +      I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { +    BugReport *R = new BugReport(*LeakBugType, +        "Opened file is never closed; potential resource leak", ErrNode); +    R->markInteresting(*I); +    C.emitReport(R); +  } +} + +// Check various ways a symbol can be invalidated. +// Stop tracking symbols when a value escapes as a result of checkBind. +// A value escapes in three possible cases: +// (1) We are binding to something that is not a memory region. +// (2) We are binding to a MemRegion that does not have stack storage +// (3) We are binding to a MemRegion with stack storage that the store +//     does not understand. +void SimpleStreamChecker::checkBind(SVal loc, SVal val, const Stmt *S, +                                    CheckerContext &C) const { +  // Are we storing to something that causes the value to "escape"? +  bool escapes = true; +  ProgramStateRef state = C.getState(); + +  if (loc::MemRegionVal *regionLoc = dyn_cast<loc::MemRegionVal>(&loc)) { +    escapes = !regionLoc->getRegion()->hasStackStorage(); + +    if (!escapes) { +      // To test (3), generate a new state with the binding added.  If it is +      // the same state, then it escapes (since the store cannot represent +      // the binding). Do this only if we know that the store is not supposed +      // to generate the same state. +      SVal StoredVal = state->getSVal(regionLoc->getRegion()); +      if (StoredVal != val) +        escapes = (state == (state->bindLoc(*regionLoc, val))); +    } +  } + +  // If our store can represent the binding and we aren't storing to something +  // that doesn't have local storage then just return the state and +  // continue as is. +  if (!escapes) +    return; + +  // Otherwise, find all symbols referenced by 'val' that we are tracking +  // and stop tracking them. +  state = state->scanReachableSymbols<StopTrackingCallback>(val).getState(); +  C.addTransition(state); +} + +bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ +  // If it's not in a system header, assume it might close a file. +  if (!Call.isInSystemHeader()) +    return false; + +  // Handle cases where we know a buffer's /address/ can escape. +  if (Call.argumentsMayEscape()) +    return false; + +  // Note, even though fclose closes the file, we do not list it here +  // since the checker is modeling the call. + +  return true; +} + +// If the symbol we are tracking is invalidated, do not track the symbol as +// we cannot reason about it anymore. +ProgramStateRef +SimpleStreamChecker::checkRegionChanges(ProgramStateRef State, +    const StoreManager::InvalidatedSymbols *invalidated, +    ArrayRef<const MemRegion *> ExplicitRegions, +    ArrayRef<const MemRegion *> Regions, +    const CallEvent *Call) const { + +  if (!invalidated || invalidated->empty()) +    return State; + +  // If it's a call which might close the file, we assume that all regions +  // (explicit and implicit) escaped. Otherwise, whitelist explicit pointers +  // (the parameters to the call); we still can track them. +  llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; +  if (!Call || guaranteedNotToCloseFile(*Call)) { +    for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), +        E = ExplicitRegions.end(); I != E; ++I) { +      if (const SymbolicRegion *R = (*I)->StripCasts()->getAs<SymbolicRegion>()) +        WhitelistedSymbols.insert(R->getSymbol()); +    } +  } + +  for (StoreManager::InvalidatedSymbols::const_iterator I=invalidated->begin(), +       E = invalidated->end(); I!=E; ++I) { +    SymbolRef sym = *I; +    if (WhitelistedSymbols.count(sym)) +      continue; +    // The symbol escaped. Optimistically, assume that the corresponding file +    // handle will be closed somewhere else. +    State = State->remove<StreamMap>(sym); +  } +  return State; +} + +void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { +  if (IIfopen) +    return; +  IIfopen = &Ctx.Idents.get("fopen"); +  IIfclose = &Ctx.Idents.get("fclose"); +} + +void ento::registerSimpleStreamChecker(CheckerManager &mgr) { +  mgr.registerChecker<SimpleStreamChecker>(); +}  | 
