brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.6 KiB · 5152624 Raw
241 lines · cpp
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-exception6//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 before13//   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/CallDescription.h"21#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"23#include <utility>24 25using namespace clang;26using namespace ento;27 28namespace {29typedef SmallVector<SymbolRef, 2> SymbolVector;30 31struct StreamState {32private:33  enum Kind { Opened, Closed } K;34  StreamState(Kind InK) : K(InK) { }35 36public:37  bool isOpened() const { return K == Opened; }38  bool isClosed() const { return K == Closed; }39 40  static StreamState getOpened() { return StreamState(Opened); }41  static StreamState getClosed() { return StreamState(Closed); }42 43  bool operator==(const StreamState &X) const {44    return K == X.K;45  }46  void Profile(llvm::FoldingSetNodeID &ID) const {47    ID.AddInteger(K);48  }49};50 51class SimpleStreamChecker : public Checker<check::PostCall,52                                           check::PreCall,53                                           check::DeadSymbols,54                                           check::PointerEscape> {55  const CallDescription OpenFn{CDM::CLibrary, {"fopen"}, 2};56  const CallDescription CloseFn{CDM::CLibrary, {"fclose"}, 1};57 58  const BugType DoubleCloseBugType{this, "Double fclose",59                                   "Unix Stream API Error"};60  const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error",61                            /*SuppressOnSink=*/true};62 63  void reportDoubleClose(SymbolRef FileDescSym,64                         const CallEvent &Call,65                         CheckerContext &C) const;66 67  void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,68                   ExplodedNode *ErrNode) const;69 70  bool guaranteedNotToCloseFile(const CallEvent &Call) const;71 72public:73  /// Process fopen.74  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;75  /// Process fclose.76  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;77 78  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;79 80  /// Stop tracking addresses which escape.81  ProgramStateRef checkPointerEscape(ProgramStateRef State,82                                    const InvalidatedSymbols &Escaped,83                                    const CallEvent *Call,84                                    PointerEscapeKind Kind) const;85};86 87} // end anonymous namespace88 89/// The state of the checker is a map from tracked stream symbols to their90/// state. Let's store it in the ProgramState.91REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)92 93void SimpleStreamChecker::checkPostCall(const CallEvent &Call,94                                        CheckerContext &C) const {95  if (!OpenFn.matches(Call))96    return;97 98  // Get the symbolic value corresponding to the file handle.99  SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();100  if (!FileDesc)101    return;102 103  // Generate the next transition (an edge in the exploded graph).104  ProgramStateRef State = C.getState();105  State = State->set<StreamMap>(FileDesc, StreamState::getOpened());106  C.addTransition(State);107}108 109void SimpleStreamChecker::checkPreCall(const CallEvent &Call,110                                       CheckerContext &C) const {111  if (!CloseFn.matches(Call))112    return;113 114  // Get the symbolic value corresponding to the file handle.115  SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();116  if (!FileDesc)117    return;118 119  // Check if the stream has already been closed.120  ProgramStateRef State = C.getState();121  const StreamState *SS = State->get<StreamMap>(FileDesc);122  if (SS && SS->isClosed()) {123    reportDoubleClose(FileDesc, Call, C);124    return;125  }126 127  // Generate the next transition, in which the stream is closed.128  State = State->set<StreamMap>(FileDesc, StreamState::getClosed());129  C.addTransition(State);130}131 132static bool isLeaked(SymbolRef Sym, const StreamState &SS,133                     bool IsSymDead, ProgramStateRef State) {134  if (IsSymDead && SS.isOpened()) {135    // If a symbol is NULL, assume that fopen failed on this path.136    // A symbol should only be considered leaked if it is non-null.137    ConstraintManager &CMgr = State->getConstraintManager();138    ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);139    return !OpenFailed.isConstrainedTrue();140  }141  return false;142}143 144void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,145                                           CheckerContext &C) const {146  ProgramStateRef State = C.getState();147  SymbolVector LeakedStreams;148  StreamMapTy TrackedStreams = State->get<StreamMap>();149  for (auto [Sym, StreamStatus] : TrackedStreams) {150    bool IsSymDead = SymReaper.isDead(Sym);151 152    // Collect leaked symbols.153    if (isLeaked(Sym, StreamStatus, IsSymDead, State))154      LeakedStreams.push_back(Sym);155 156    // Remove the dead symbol from the streams map.157    if (IsSymDead)158      State = State->remove<StreamMap>(Sym);159  }160 161  ExplodedNode *N = C.generateNonFatalErrorNode(State);162  if (!N)163    return;164  reportLeaks(LeakedStreams, C, N);165}166 167void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,168                                            const CallEvent &Call,169                                            CheckerContext &C) const {170  // We reached a bug, stop exploring the path here by generating a sink.171  ExplodedNode *ErrNode = C.generateErrorNode();172  // If we've already reached this node on another path, return.173  if (!ErrNode)174    return;175 176  // Generate the report.177  auto R = std::make_unique<PathSensitiveBugReport>(178      DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);179  R->addRange(Call.getSourceRange());180  R->markInteresting(FileDescSym);181  C.emitReport(std::move(R));182}183 184void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,185                                      CheckerContext &C,186                                      ExplodedNode *ErrNode) const {187  // Attach bug reports to the leak node.188  // TODO: Identify the leaked file descriptor.189  for (SymbolRef LeakedStream : LeakedStreams) {190    auto R = std::make_unique<PathSensitiveBugReport>(191        LeakBugType, "Opened file is never closed; potential resource leak",192        ErrNode);193    R->markInteresting(LeakedStream);194    C.emitReport(std::move(R));195  }196}197 198bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{199  // If it's not in a system header, assume it might close a file.200  if (!Call.isInSystemHeader())201    return false;202 203  // Handle cases where we know a buffer's /address/ can escape.204  if (Call.argumentsMayEscape())205    return false;206 207  // Note, even though fclose closes the file, we do not list it here208  // since the checker is modeling the call.209 210  return true;211}212 213// If the pointer we are tracking escaped, do not track the symbol as214// we cannot reason about it anymore.215ProgramStateRef216SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,217                                        const InvalidatedSymbols &Escaped,218                                        const CallEvent *Call,219                                        PointerEscapeKind Kind) const {220  // If we know that the call cannot close a file, there is nothing to do.221  if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {222    return State;223  }224 225  for (SymbolRef Sym : Escaped) {226    // The symbol escaped. Optimistically, assume that the corresponding file227    // handle will be closed somewhere else.228    State = State->remove<StreamMap>(Sym);229  }230  return State;231}232 233void ento::registerSimpleStreamChecker(CheckerManager &mgr) {234  mgr.registerChecker<SimpleStreamChecker>();235}236 237// This checker should be enabled regardless of how language options are set.238bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {239  return true;240}241