brintos

brintos / llvm-project-archived public Read only

0
0
Text · 80.6 KiB · 6481b76 Raw
2154 lines · cpp
1//===-- StreamChecker.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// This file defines checkers that model and check stream handling functions.10//11//===----------------------------------------------------------------------===//12 13#include "NoOwnershipChangeVisitor.h"14#include "clang/ASTMatchers/ASTMatchFinder.h"15#include "clang/ASTMatchers/ASTMatchers.h"16#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"17#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"18#include "clang/StaticAnalyzer/Core/Checker.h"19#include "clang/StaticAnalyzer/Core/CheckerManager.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 "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"24#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"25#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"26#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"27#include "llvm/ADT/Sequence.h"28#include <functional>29#include <optional>30 31using namespace clang;32using namespace ento;33using namespace std::placeholders;34 35//===----------------------------------------------------------------------===//36// Definition of state data structures.37//===----------------------------------------------------------------------===//38 39namespace {40 41struct FnDescription;42 43/// State of the stream error flags.44/// Sometimes it is not known to the checker what error flags are set.45/// This is indicated by setting more than one flag to true.46/// This is an optimization to avoid state splits.47/// A stream can either be in FEOF or FERROR but not both at the same time.48/// Multiple flags are set to handle the corresponding states together.49struct StreamErrorState {50  /// The stream can be in state where none of the error flags set.51  bool NoError = true;52  /// The stream can be in state where the EOF indicator is set.53  bool FEof = false;54  /// The stream can be in state where the error indicator is set.55  bool FError = false;56 57  bool isNoError() const { return NoError && !FEof && !FError; }58  bool isFEof() const { return !NoError && FEof && !FError; }59  bool isFError() const { return !NoError && !FEof && FError; }60 61  bool operator==(const StreamErrorState &ES) const {62    return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;63  }64 65  bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }66 67  StreamErrorState operator|(const StreamErrorState &E) const {68    return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};69  }70 71  StreamErrorState operator&(const StreamErrorState &E) const {72    return {NoError && E.NoError, FEof && E.FEof, FError && E.FError};73  }74 75  StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; }76 77  /// Returns if the StreamErrorState is a valid object.78  operator bool() const { return NoError || FEof || FError; }79 80  LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); }81  LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &os) const {82    os << "NoError: " << NoError << ", FEof: " << FEof83       << ", FError: " << FError;84  }85 86  void Profile(llvm::FoldingSetNodeID &ID) const {87    ID.AddBoolean(NoError);88    ID.AddBoolean(FEof);89    ID.AddBoolean(FError);90  }91};92 93const StreamErrorState ErrorNone{true, false, false};94const StreamErrorState ErrorFEof{false, true, false};95const StreamErrorState ErrorFError{false, false, true};96 97/// Full state information about a stream pointer.98struct StreamState {99  /// The last file operation called in the stream.100  /// Can be nullptr.101  const FnDescription *LastOperation;102 103  /// State of a stream symbol.104  enum KindTy {105    Opened, /// Stream is opened.106    Closed, /// Closed stream (an invalid stream pointer after it was closed).107    OpenFailed /// The last open operation has failed.108  } State;109 110  StringRef getKindStr() const {111    switch (State) {112    case Opened:113      return "Opened";114    case Closed:115      return "Closed";116    case OpenFailed:117      return "OpenFailed";118    }119    llvm_unreachable("Unknown StreamState!");120  }121 122  /// State of the error flags.123  /// Ignored in non-opened stream state but must be NoError.124  StreamErrorState const ErrorState;125 126  /// Indicate if the file has an "indeterminate file position indicator".127  /// This can be set at a failing read or write or seek operation.128  /// If it is set no more read or write is allowed.129  /// This value is not dependent on the stream error flags:130  /// The error flag may be cleared with `clearerr` but the file position131  /// remains still indeterminate.132  /// This value applies to all error states in ErrorState except FEOF.133  /// An EOF+indeterminate state is the same as EOF state.134  bool const FilePositionIndeterminate = false;135 136  StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES,137              bool IsFilePositionIndeterminate)138      : LastOperation(L), State(S), ErrorState(ES),139        FilePositionIndeterminate(IsFilePositionIndeterminate) {140    assert((!ES.isFEof() || !IsFilePositionIndeterminate) &&141           "FilePositionIndeterminate should be false in FEof case.");142    assert((State == Opened || ErrorState.isNoError()) &&143           "ErrorState should be None in non-opened stream state.");144  }145 146  bool isOpened() const { return State == Opened; }147  bool isClosed() const { return State == Closed; }148  bool isOpenFailed() const { return State == OpenFailed; }149 150  bool operator==(const StreamState &X) const {151    // In not opened state error state should always NoError, so comparison152    // here is no problem.153    return LastOperation == X.LastOperation && State == X.State &&154           ErrorState == X.ErrorState &&155           FilePositionIndeterminate == X.FilePositionIndeterminate;156  }157 158  static StreamState getOpened(const FnDescription *L,159                               const StreamErrorState &ES = ErrorNone,160                               bool IsFilePositionIndeterminate = false) {161    return StreamState{L, Opened, ES, IsFilePositionIndeterminate};162  }163  static StreamState getClosed(const FnDescription *L) {164    return StreamState{L, Closed, {}, false};165  }166  static StreamState getOpenFailed(const FnDescription *L) {167    return StreamState{L, OpenFailed, {}, false};168  }169 170  LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); }171  LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &os) const;172 173  void Profile(llvm::FoldingSetNodeID &ID) const {174    ID.AddPointer(LastOperation);175    ID.AddInteger(State);176    ErrorState.Profile(ID);177    ID.AddBoolean(FilePositionIndeterminate);178  }179};180 181} // namespace182 183// This map holds the state of a stream.184// The stream is identified with a SymbolRef that is created when a stream185// opening function is modeled by the checker.186REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)187 188//===----------------------------------------------------------------------===//189// StreamChecker class and utility functions.190//===----------------------------------------------------------------------===//191 192namespace {193 194class StreamChecker;195using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,196                                   const CallEvent &, CheckerContext &)>;197 198using ArgNoTy = unsigned int;199static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();200 201const char *FeofNote = "Assuming stream reaches end-of-file here";202const char *FerrorNote = "Assuming this stream operation fails";203 204struct FnDescription {205  FnCheck PreFn;206  FnCheck EvalFn;207  ArgNoTy StreamArgNo;208};209 210LLVM_DUMP_METHOD void StreamState::dumpToStream(llvm::raw_ostream &os) const {211  os << "{Kind: " << getKindStr() << ", Last operation: " << LastOperation212     << ", ErrorState: ";213  ErrorState.dumpToStream(os);214  os << ", FilePos: " << (FilePositionIndeterminate ? "Indeterminate" : "OK")215     << '}';216}217 218/// Get the value of the stream argument out of the passed call event.219/// The call should contain a function that is described by Desc.220SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {221  assert(Desc && Desc->StreamArgNo != ArgNone &&222         "Try to get a non-existing stream argument.");223  return Call.getArgSVal(Desc->StreamArgNo);224}225 226/// Create a conjured symbol return value for a call expression.227DefinedSVal makeRetVal(CheckerContext &C, ConstCFGElementRef Elem) {228  return C.getSValBuilder()229      .conjureSymbolVal(/*symbolTag=*/nullptr, Elem, C.getLocationContext(),230                        C.blockCount())231      .castAs<DefinedSVal>();232}233 234ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C,235                                  const CallExpr *CE, ConstCFGElementRef Elem) {236  DefinedSVal RetVal = makeRetVal(C, Elem);237  State = State->BindExpr(CE, C.getLocationContext(), RetVal);238  State = State->assume(RetVal, true);239  assert(State && "Assumption on new value should not fail.");240  return State;241}242 243ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State,244                        CheckerContext &C, const CallExpr *CE) {245  State = State->BindExpr(CE, C.getLocationContext(),246                          C.getSValBuilder().makeIntVal(Value, CE->getType()));247  return State;248}249 250inline void assertStreamStateOpened(const StreamState *SS) {251  assert(SS->isOpened() && "Stream is expected to be opened");252}253 254class StreamChecker : public Checker<check::PreCall, eval::Call,255                                     check::DeadSymbols, check::PointerEscape,256                                     check::ASTDecl<TranslationUnitDecl>> {257  BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"};258  BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"};259  BugType BT_UseAfterOpenFailed{this, "Invalid stream",260                                "Stream handling error"};261  BugType BT_IndeterminatePosition{this, "Invalid stream state",262                                   "Stream handling error"};263  BugType BT_IllegalWhence{this, "Illegal whence argument",264                           "Stream handling error"};265  BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};266  BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",267                          /*SuppressOnSink =*/true};268 269public:270  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;271  bool evalCall(const CallEvent &Call, CheckerContext &C) const;272  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;273  ProgramStateRef checkPointerEscape(ProgramStateRef State,274                                     const InvalidatedSymbols &Escaped,275                                     const CallEvent *Call,276                                     PointerEscapeKind Kind) const;277 278  /// Finds the declarations of 'FILE *stdin, *stdout, *stderr'.279  void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &,280                    BugReporter &) const;281 282  const BugType *getBT_StreamEof() const { return &BT_StreamEof; }283  const BugType *getBT_IndeterminatePosition() const {284    return &BT_IndeterminatePosition;285  }286 287  /// Assumes that the result of 'fopen' can't alias with the pointee of288  /// 'stdin', 'stdout' or 'stderr'.289  ProgramStateRef assumeNoAliasingWithStdStreams(ProgramStateRef State,290                                                 DefinedSVal RetVal,291                                                 CheckerContext &C) const;292 293  const NoteTag *constructSetEofNoteTag(CheckerContext &C,294                                        SymbolRef StreamSym) const {295    return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {296      if (!BR.isInteresting(StreamSym) ||297          &BR.getBugType() != this->getBT_StreamEof())298        return "";299 300      BR.markNotInteresting(StreamSym);301 302      return FeofNote;303    });304  }305 306  const NoteTag *constructSetErrorNoteTag(CheckerContext &C,307                                          SymbolRef StreamSym) const {308    return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {309      if (!BR.isInteresting(StreamSym) ||310          &BR.getBugType() != this->getBT_IndeterminatePosition())311        return "";312 313      BR.markNotInteresting(StreamSym);314 315      return FerrorNote;316    });317  }318 319  const NoteTag *constructSetEofOrErrorNoteTag(CheckerContext &C,320                                               SymbolRef StreamSym) const {321    return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {322      if (!BR.isInteresting(StreamSym))323        return "";324 325      if (&BR.getBugType() == this->getBT_StreamEof()) {326        BR.markNotInteresting(StreamSym);327        return FeofNote;328      }329      if (&BR.getBugType() == this->getBT_IndeterminatePosition()) {330        BR.markNotInteresting(StreamSym);331        return FerrorNote;332      }333 334      return "";335    });336  }337 338  /// If true, evaluate special testing stream functions.339  bool TestMode = false;340 341  /// If true, generate failure branches for cases that are often not checked.342  bool PedanticMode = false;343 344  const CallDescription FCloseDesc = {CDM::CLibrary, {"fclose"}, 1};345 346private:347  CallDescriptionMap<FnDescription> FnDescriptions = {348      {{CDM::CLibrary, {"fopen"}, 2},349       {nullptr, &StreamChecker::evalFopen, ArgNone}},350      {{CDM::CLibrary, {"fdopen"}, 2},351       {nullptr, &StreamChecker::evalFopen, ArgNone}},352      {{CDM::CLibrary, {"freopen"}, 3},353       {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},354      {{CDM::CLibrary, {"tmpfile"}, 0},355       {nullptr, &StreamChecker::evalFopen, ArgNone}},356      {FCloseDesc, {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},357      {{CDM::CLibrary, {"fread"}, 4},358       {&StreamChecker::preRead,359        std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},360      {{CDM::CLibrary, {"fwrite"}, 4},361       {&StreamChecker::preWrite,362        std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},363      {{CDM::CLibrary, {"fgetc"}, 1},364       {&StreamChecker::preRead,365        std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},366      {{CDM::CLibrary, {"fgets"}, 3},367       {&StreamChecker::preRead,368        std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}},369      {{CDM::CLibrary, {"getc"}, 1},370       {&StreamChecker::preRead,371        std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}},372      {{CDM::CLibrary, {"fputc"}, 2},373       {&StreamChecker::preWrite,374        std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},375      {{CDM::CLibrary, {"fputs"}, 2},376       {&StreamChecker::preWrite,377        std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},378      {{CDM::CLibrary, {"putc"}, 2},379       {&StreamChecker::preWrite,380        std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}},381      {{CDM::CLibrary, {"fprintf"}},382       {&StreamChecker::preWrite,383        std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},384      {{CDM::CLibrary, {"vfprintf"}, 3},385       {&StreamChecker::preWrite,386        std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},387      {{CDM::CLibrary, {"fscanf"}},388       {&StreamChecker::preRead,389        std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},390      {{CDM::CLibrary, {"vfscanf"}, 3},391       {&StreamChecker::preRead,392        std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},393      {{CDM::CLibrary, {"ungetc"}, 2},394       {&StreamChecker::preWrite,395        std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},396      {{CDM::CLibrary, {"getdelim"}, 4},397       {&StreamChecker::preRead,398        std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}},399      {{CDM::CLibrary, {"getline"}, 3},400       {&StreamChecker::preRead,401        std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}},402      {{CDM::CLibrary, {"fseek"}, 3},403       {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},404      {{CDM::CLibrary, {"fseeko"}, 3},405       {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},406      {{CDM::CLibrary, {"ftell"}, 1},407       {&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},408      {{CDM::CLibrary, {"ftello"}, 1},409       {&StreamChecker::preWrite, &StreamChecker::evalFtell, 0}},410      {{CDM::CLibrary, {"fflush"}, 1},411       {&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}},412      {{CDM::CLibrary, {"rewind"}, 1},413       {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},414      {{CDM::CLibrary, {"fgetpos"}, 2},415       {&StreamChecker::preWrite, &StreamChecker::evalFgetpos, 0}},416      {{CDM::CLibrary, {"fsetpos"}, 2},417       {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},418      {{CDM::CLibrary, {"clearerr"}, 1},419       {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},420      {{CDM::CLibrary, {"feof"}, 1},421       {&StreamChecker::preDefault,422        std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),423        0}},424      {{CDM::CLibrary, {"ferror"}, 1},425       {&StreamChecker::preDefault,426        std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),427        0}},428      {{CDM::CLibrary, {"fileno"}, 1},429       {&StreamChecker::preDefault, &StreamChecker::evalFileno, 0}},430  };431 432  CallDescriptionMap<FnDescription> FnTestDescriptions = {433      {{CDM::SimpleFunc, {"StreamTesterChecker_make_feof_stream"}, 1},434       {nullptr,435        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof,436                  false),437        0}},438      {{CDM::SimpleFunc, {"StreamTesterChecker_make_ferror_stream"}, 1},439       {nullptr,440        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,441                  ErrorFError, false),442        0}},443      {{CDM::SimpleFunc,444        {"StreamTesterChecker_make_ferror_indeterminate_stream"},445        1},446       {nullptr,447        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,448                  ErrorFError, true),449        0}},450  };451 452  /// Expanded value of EOF, empty before initialization.453  mutable std::optional<int> EofVal;454  /// Expanded value of SEEK_SET, 0 if not found.455  mutable int SeekSetVal = 0;456  /// Expanded value of SEEK_CUR, 1 if not found.457  mutable int SeekCurVal = 1;458  /// Expanded value of SEEK_END, 2 if not found.459  mutable int SeekEndVal = 2;460  /// The built-in va_list type is platform-specific461  mutable QualType VaListType;462 463  mutable const VarDecl *StdinDecl = nullptr;464  mutable const VarDecl *StdoutDecl = nullptr;465  mutable const VarDecl *StderrDecl = nullptr;466 467  void evalFopen(const FnDescription *Desc, const CallEvent &Call,468                 CheckerContext &C) const;469 470  void preFreopen(const FnDescription *Desc, const CallEvent &Call,471                  CheckerContext &C) const;472  void evalFreopen(const FnDescription *Desc, const CallEvent &Call,473                   CheckerContext &C) const;474 475  void evalFclose(const FnDescription *Desc, const CallEvent &Call,476                  CheckerContext &C) const;477 478  void preRead(const FnDescription *Desc, const CallEvent &Call,479               CheckerContext &C) const;480 481  void preWrite(const FnDescription *Desc, const CallEvent &Call,482                CheckerContext &C) const;483 484  void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,485                       CheckerContext &C, bool IsFread) const;486 487  void evalFgetx(const FnDescription *Desc, const CallEvent &Call,488                 CheckerContext &C, bool SingleChar) const;489 490  void evalFputx(const FnDescription *Desc, const CallEvent &Call,491                 CheckerContext &C, bool IsSingleChar) const;492 493  void evalFprintf(const FnDescription *Desc, const CallEvent &Call,494                   CheckerContext &C) const;495 496  void evalFscanf(const FnDescription *Desc, const CallEvent &Call,497                  CheckerContext &C) const;498 499  void evalUngetc(const FnDescription *Desc, const CallEvent &Call,500                  CheckerContext &C) const;501 502  void evalGetdelim(const FnDescription *Desc, const CallEvent &Call,503                    CheckerContext &C) const;504 505  void preFseek(const FnDescription *Desc, const CallEvent &Call,506                CheckerContext &C) const;507  void evalFseek(const FnDescription *Desc, const CallEvent &Call,508                 CheckerContext &C) const;509 510  void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,511                   CheckerContext &C) const;512 513  void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,514                   CheckerContext &C) const;515 516  void evalFtell(const FnDescription *Desc, const CallEvent &Call,517                 CheckerContext &C) const;518 519  void evalRewind(const FnDescription *Desc, const CallEvent &Call,520                  CheckerContext &C) const;521 522  void preDefault(const FnDescription *Desc, const CallEvent &Call,523                  CheckerContext &C) const;524 525  void evalClearerr(const FnDescription *Desc, const CallEvent &Call,526                    CheckerContext &C) const;527 528  void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,529                      CheckerContext &C,530                      const StreamErrorState &ErrorKind) const;531 532  void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,533                         CheckerContext &C, const StreamErrorState &ErrorKind,534                         bool Indeterminate) const;535 536  void preFflush(const FnDescription *Desc, const CallEvent &Call,537                 CheckerContext &C) const;538 539  void evalFflush(const FnDescription *Desc, const CallEvent &Call,540                  CheckerContext &C) const;541 542  void evalFileno(const FnDescription *Desc, const CallEvent &Call,543                  CheckerContext &C) const;544 545  /// Check that the stream (in StreamVal) is not NULL.546  /// If it can only be NULL a fatal error is emitted and nullptr returned.547  /// Otherwise the return value is a new state where the stream is constrained548  /// to be non-null.549  ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,550                                      CheckerContext &C,551                                      ProgramStateRef State) const;552 553  /// Check that the stream is the opened state.554  /// If the stream is known to be not opened an error is generated555  /// and nullptr returned, otherwise the original state is returned.556  ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,557                                     ProgramStateRef State) const;558 559  /// Check that the stream has not an invalid ("indeterminate") file position,560  /// generate warning for it.561  /// (EOF is not an invalid position.)562  /// The returned state can be nullptr if a fatal error was generated.563  /// It can return non-null state if the stream has not an invalid position or564  /// there is execution path with non-invalid position.565  ProgramStateRef566  ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,567                                    ProgramStateRef State) const;568 569  /// Check the legality of the 'whence' argument of 'fseek'.570  /// Generate error and return nullptr if it is found to be illegal.571  /// Otherwise returns the state.572  /// (State is not changed here because the "whence" value is already known.)573  ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,574                                           ProgramStateRef State) const;575 576  /// Generate warning about stream in EOF state.577  /// There will be always a state transition into the passed State,578  /// by the new non-fatal error node or (if failed) a normal transition,579  /// to ensure uniform handling.580  void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,581                         ProgramStateRef State) const;582 583  /// Emit resource leak warnings for the given symbols.584  /// Createn a non-fatal error node for these, and returns it (if any warnings585  /// were generated). Return value is non-null.586  ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,587                            CheckerContext &C, ExplodedNode *Pred) const;588 589  /// Find the description data of the function called by a call event.590  /// Returns nullptr if no function is recognized.591  const FnDescription *lookupFn(const CallEvent &Call) const {592    // Recognize "global C functions" with only integral or pointer arguments593    // (and matching name) as stream functions.594    for (auto *P : Call.parameters()) {595      QualType T = P->getType();596      if (!T->isIntegralOrEnumerationType() && !T->isPointerType() &&597          T.getCanonicalType() != VaListType)598        return nullptr;599    }600 601    return FnDescriptions.lookup(Call);602  }603 604  /// Generate a message for BugReporterVisitor if the stored symbol is605  /// marked as interesting by the actual bug report.606  const NoteTag *constructLeakNoteTag(CheckerContext &C, SymbolRef StreamSym,607                                      const std::string &Message) const {608    return C.getNoteTag([this, StreamSym,609                         Message](PathSensitiveBugReport &BR) -> std::string {610      if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak)611        return Message;612      return "";613    });614  }615 616  void initMacroValues(const Preprocessor &PP) const {617    if (EofVal)618      return;619 620    if (const std::optional<int> OptInt = tryExpandAsInteger("EOF", PP))621      EofVal = *OptInt;622    else623      EofVal = -1;624    if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_SET", PP))625      SeekSetVal = *OptInt;626    if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_END", PP))627      SeekEndVal = *OptInt;628    if (const std::optional<int> OptInt = tryExpandAsInteger("SEEK_CUR", PP))629      SeekCurVal = *OptInt;630  }631 632  /// Searches for the ExplodedNode where the file descriptor was acquired for633  /// StreamSym.634  static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,635                                                SymbolRef StreamSym,636                                                CheckerContext &C);637};638 639struct StreamOperationEvaluator {640  SValBuilder &SVB;641  const ASTContext &ACtx;642 643  SymbolRef StreamSym = nullptr;644  const StreamState *SS = nullptr;645  const CallExpr *CE = nullptr;646  std::optional<ConstCFGElementRef> Elem;647  StreamErrorState NewES;648 649  StreamOperationEvaluator(CheckerContext &C)650      : SVB(C.getSValBuilder()), ACtx(C.getASTContext()) {651    ;652  }653 654  bool Init(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C,655            ProgramStateRef State) {656    StreamSym = getStreamArg(Desc, Call).getAsSymbol();657    if (!StreamSym)658      return false;659    SS = State->get<StreamMap>(StreamSym);660    if (!SS)661      return false;662    NewES = SS->ErrorState;663    CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());664    if (!CE)665      return false;666    Elem = Call.getCFGElementRef();667 668    assertStreamStateOpened(SS);669 670    return true;671  }672 673  bool isStreamEof() const { return SS->ErrorState == ErrorFEof; }674 675  NonLoc getZeroVal(const CallEvent &Call) {676    return *SVB.makeZeroVal(Call.getResultType()).getAs<NonLoc>();677  }678 679  ProgramStateRef setStreamState(ProgramStateRef State,680                                 const StreamState &NewSS) {681    NewES = NewSS.ErrorState;682    return State->set<StreamMap>(StreamSym, NewSS);683  }684 685  ProgramStateRef makeAndBindRetVal(ProgramStateRef State, CheckerContext &C) {686    NonLoc RetVal = makeRetVal(C, Elem.value()).castAs<NonLoc>();687    return State->BindExpr(CE, C.getLocationContext(), RetVal);688  }689 690  ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,691                                  uint64_t Val) {692    return State->BindExpr(CE, C.getLocationContext(),693                           SVB.makeIntVal(Val, CE->getCallReturnType(ACtx)));694  }695 696  ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C,697                                  SVal Val) {698    return State->BindExpr(CE, C.getLocationContext(), Val);699  }700 701  ProgramStateRef bindNullReturnValue(ProgramStateRef State,702                                      CheckerContext &C) {703    return State->BindExpr(CE, C.getLocationContext(),704                           C.getSValBuilder().makeNullWithType(CE->getType()));705  }706 707  ProgramStateRef assumeBinOpNN(ProgramStateRef State,708                                BinaryOperator::Opcode Op, NonLoc LHS,709                                NonLoc RHS) {710    auto Cond = SVB.evalBinOpNN(State, Op, LHS, RHS, SVB.getConditionType())711                    .getAs<DefinedOrUnknownSVal>();712    if (!Cond)713      return nullptr;714    return State->assume(*Cond, true);715  }716 717  ConstraintManager::ProgramStatePair718  makeRetValAndAssumeDual(ProgramStateRef State, CheckerContext &C) {719    DefinedSVal RetVal = makeRetVal(C, Elem.value());720    State = State->BindExpr(CE, C.getLocationContext(), RetVal);721    return C.getConstraintManager().assumeDual(State, RetVal);722  }723 724  const NoteTag *getFailureNoteTag(const StreamChecker *Ch, CheckerContext &C) {725    bool SetFeof = NewES.FEof && !SS->ErrorState.FEof;726    bool SetFerror = NewES.FError && !SS->ErrorState.FError;727    if (SetFeof && !SetFerror)728      return Ch->constructSetEofNoteTag(C, StreamSym);729    if (!SetFeof && SetFerror)730      return Ch->constructSetErrorNoteTag(C, StreamSym);731    if (SetFeof && SetFerror)732      return Ch->constructSetEofOrErrorNoteTag(C, StreamSym);733    return nullptr;734  }735};736 737} // end anonymous namespace738 739//===----------------------------------------------------------------------===//740// Definition of NoStreamStateChangeVisitor.741//===----------------------------------------------------------------------===//742 743namespace {744class NoStreamStateChangeVisitor final : public NoOwnershipChangeVisitor {745protected:746  /// Syntactically checks whether the callee is a closing function. Since747  /// we have no path-sensitive information on this call (we would need a748  /// CallEvent instead of a CallExpr for that), its possible that a749  /// closing function was called indirectly through a function pointer,750  /// but we are not able to tell, so this is a best effort analysis.751  bool isClosingCallAsWritten(const CallExpr &Call) const {752    const auto *StreamChk = static_cast<const StreamChecker *>(&Checker);753    return StreamChk->FCloseDesc.matchesAsWritten(Call);754  }755 756  bool doesFnIntendToHandleOwnership(const Decl *Callee,757                                     ASTContext &ACtx) final {758    const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);759 760    // Given that the stack frame was entered, the body should always be761    // theoretically obtainable. In case of body farms, the synthesized body762    // is not attached to declaration, thus triggering the '!FD->hasBody()'763    // branch. That said, would a synthesized body ever intend to handle764    // ownership? As of today they don't. And if they did, how would we765    // put notes inside it, given that it doesn't match any source locations?766    if (!FD || !FD->hasBody())767      return false;768    using namespace clang::ast_matchers;769 770    auto Matches =771        match(findAll(callExpr().bind("call")), *FD->getBody(), ACtx);772    for (BoundNodes Match : Matches) {773      if (const auto *Call = Match.getNodeAs<CallExpr>("call"))774        if (isClosingCallAsWritten(*Call))775          return true;776    }777    // TODO: Ownership might change with an attempt to store stream object, not778    // only through closing it. Check for attempted stores as well.779    return false;780  }781 782  bool hasResourceStateChanged(ProgramStateRef CallEnterState,783                               ProgramStateRef CallExitEndState) final {784    return CallEnterState->get<StreamMap>(Sym) !=785           CallExitEndState->get<StreamMap>(Sym);786  }787 788  PathDiagnosticPieceRef emitNote(const ExplodedNode *N) override {789    PathDiagnosticLocation L = PathDiagnosticLocation::create(790        N->getLocation(),791        N->getState()->getStateManager().getContext().getSourceManager());792    return std::make_shared<PathDiagnosticEventPiece>(793        L, "Returning without closing stream object or storing it for later "794           "release");795  }796 797public:798  NoStreamStateChangeVisitor(SymbolRef Sym, const StreamChecker *Checker)799      : NoOwnershipChangeVisitor(Sym, Checker) {}800};801 802} // end anonymous namespace803 804const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,805                                                      SymbolRef StreamSym,806                                                      CheckerContext &C) {807  ProgramStateRef State = N->getState();808  // When bug type is resource leak, exploded node N may not have state info809  // for leaked file descriptor, but predecessor should have it.810  if (!State->get<StreamMap>(StreamSym))811    N = N->getFirstPred();812 813  const ExplodedNode *Pred = N;814  while (N) {815    State = N->getState();816    if (!State->get<StreamMap>(StreamSym))817      return Pred;818    Pred = N;819    N = N->getFirstPred();820  }821 822  return nullptr;823}824 825static std::optional<int64_t> getKnownValue(ProgramStateRef State, SVal V) {826  SValBuilder &SVB = State->getStateManager().getSValBuilder();827  if (const llvm::APSInt *Int = SVB.getKnownValue(State, V))828    return Int->tryExtValue();829  return std::nullopt;830}831 832/// Invalidate only the requested elements instead of the whole buffer.833/// This is basically a refinement of the more generic 'escapeArgs' or834/// the plain old 'invalidateRegions'.835static ProgramStateRef836escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call,837                           unsigned BlockCount, const SubRegion *Buffer,838                           QualType ElemType, int64_t StartIndex,839                           int64_t ElementCount) {840  constexpr auto DoNotInvalidateSuperRegion =841      RegionAndSymbolInvalidationTraits::InvalidationKinds::842          TK_DoNotInvalidateSuperRegion;843 844  const LocationContext *LCtx = Call.getLocationContext();845  const ASTContext &Ctx = State->getStateManager().getContext();846  SValBuilder &SVB = State->getStateManager().getSValBuilder();847  auto &RegionManager = Buffer->getMemRegionManager();848 849  SmallVector<SVal> EscapingVals;850  EscapingVals.reserve(ElementCount);851 852  RegionAndSymbolInvalidationTraits ITraits;853  for (auto Idx : llvm::seq(StartIndex, StartIndex + ElementCount)) {854    NonLoc Index = SVB.makeArrayIndex(Idx);855    const auto *Element =856        RegionManager.getElementRegion(ElemType, Index, Buffer, Ctx);857    EscapingVals.push_back(loc::MemRegionVal(Element));858    ITraits.setTrait(Element, DoNotInvalidateSuperRegion);859  }860  return State->invalidateRegions(861      EscapingVals, Call.getCFGElementRef(), BlockCount, LCtx,862      /*CausesPointerEscape=*/false,863      /*InvalidatedSymbols=*/nullptr, &Call, &ITraits);864}865 866static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,867                                  const CallEvent &Call,868                                  ArrayRef<unsigned int> EscapingArgs) {869  auto GetArgSVal = [&Call](int Idx) { return Call.getArgSVal(Idx); };870  auto EscapingVals = to_vector(map_range(EscapingArgs, GetArgSVal));871  State = State->invalidateRegions(EscapingVals, Call.getCFGElementRef(),872                                   C.blockCount(), C.getLocationContext(),873                                   /*CausesPointerEscape=*/false,874                                   /*InvalidatedSymbols=*/nullptr);875  return State;876}877 878//===----------------------------------------------------------------------===//879// Methods of StreamChecker.880//===----------------------------------------------------------------------===//881 882void StreamChecker::checkPreCall(const CallEvent &Call,883                                 CheckerContext &C) const {884  const FnDescription *Desc = lookupFn(Call);885  if (!Desc || !Desc->PreFn)886    return;887 888  Desc->PreFn(this, Desc, Call, C);889}890 891bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {892  const FnDescription *Desc = lookupFn(Call);893  if (!Desc && TestMode)894    Desc = FnTestDescriptions.lookup(Call);895  if (!Desc || !Desc->EvalFn)896    return false;897 898  Desc->EvalFn(this, Desc, Call, C);899 900  return C.isDifferent();901}902 903ProgramStateRef StreamChecker::assumeNoAliasingWithStdStreams(904    ProgramStateRef State, DefinedSVal RetVal, CheckerContext &C) const {905  auto assumeRetNE = [&C, RetVal](ProgramStateRef State,906                                  const VarDecl *Var) -> ProgramStateRef {907    if (!Var)908      return State;909    const auto *LCtx = C.getLocationContext();910    auto &StoreMgr = C.getStoreManager();911    auto &SVB = C.getSValBuilder();912    SVal VarValue = State->getSVal(StoreMgr.getLValueVar(Var, LCtx));913    auto NoAliasState =914        SVB.evalBinOp(State, BO_NE, RetVal, VarValue, SVB.getConditionType())915            .castAs<DefinedOrUnknownSVal>();916    return State->assume(NoAliasState, true);917  };918 919  assert(State);920  State = assumeRetNE(State, StdinDecl);921  State = assumeRetNE(State, StdoutDecl);922  State = assumeRetNE(State, StderrDecl);923  assert(State);924  return State;925}926 927void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,928                              CheckerContext &C) const {929  ProgramStateRef State = C.getState();930  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());931  if (!CE)932    return;933 934  DefinedSVal RetVal = makeRetVal(C, Call.getCFGElementRef());935  SymbolRef RetSym = RetVal.getAsSymbol();936  assert(RetSym && "RetVal must be a symbol here.");937 938  State = State->BindExpr(CE, C.getLocationContext(), RetVal);939 940  // Bifurcate the state into two: one with a valid FILE* pointer, the other941  // with a NULL.942  ProgramStateRef StateNotNull, StateNull;943  std::tie(StateNotNull, StateNull) =944      C.getConstraintManager().assumeDual(State, RetVal);945 946  StateNotNull =947      StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));948  StateNull =949      StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));950 951  StateNotNull = assumeNoAliasingWithStdStreams(StateNotNull, RetVal, C);952 953  C.addTransition(StateNotNull,954                  constructLeakNoteTag(C, RetSym, "Stream opened here"));955  C.addTransition(StateNull);956}957 958void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,959                               CheckerContext &C) const {960  // Do not allow NULL as passed stream pointer but allow a closed stream.961  ProgramStateRef State = C.getState();962  State = ensureStreamNonNull(getStreamArg(Desc, Call),963                              Call.getArgExpr(Desc->StreamArgNo), C, State);964  if (!State)965    return;966 967  C.addTransition(State);968}969 970void StreamChecker::evalFreopen(const FnDescription *Desc,971                                const CallEvent &Call,972                                CheckerContext &C) const {973  ProgramStateRef State = C.getState();974 975  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());976  if (!CE)977    return;978 979  std::optional<DefinedSVal> StreamVal =980      getStreamArg(Desc, Call).getAs<DefinedSVal>();981  if (!StreamVal)982    return;983 984  SymbolRef StreamSym = StreamVal->getAsSymbol();985  // Do not care about concrete values for stream ("(FILE *)0x12345"?).986  // FIXME: Can be stdin, stdout, stderr such values?987  if (!StreamSym)988    return;989 990  // Do not handle untracked stream. It is probably escaped.991  if (!State->get<StreamMap>(StreamSym))992    return;993 994  // Generate state for non-failed case.995  // Return value is the passed stream pointer.996  // According to the documentations, the stream is closed first997  // but any close error is ignored. The state changes to (or remains) opened.998  ProgramStateRef StateRetNotNull =999      State->BindExpr(CE, C.getLocationContext(), *StreamVal);1000  // Generate state for NULL return value.1001  // Stream switches to OpenFailed state.1002  ProgramStateRef StateRetNull =1003      State->BindExpr(CE, C.getLocationContext(),1004                      C.getSValBuilder().makeNullWithType(CE->getType()));1005 1006  StateRetNotNull =1007      StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));1008  StateRetNull =1009      StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));1010 1011  C.addTransition(StateRetNotNull,1012                  constructLeakNoteTag(C, StreamSym, "Stream reopened here"));1013  C.addTransition(StateRetNull);1014}1015 1016void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,1017                               CheckerContext &C) const {1018  ProgramStateRef State = C.getState();1019  StreamOperationEvaluator E(C);1020  if (!E.Init(Desc, Call, C, State))1021    return;1022 1023  // Close the File Descriptor.1024  // Regardless if the close fails or not, stream becomes "closed"1025  // and can not be used any more.1026  State = E.setStreamState(State, StreamState::getClosed(Desc));1027 1028  // Return 0 on success, EOF on failure.1029  C.addTransition(E.bindReturnValue(State, C, 0));1030  C.addTransition(E.bindReturnValue(State, C, *EofVal));1031}1032 1033void StreamChecker::preRead(const FnDescription *Desc, const CallEvent &Call,1034                            CheckerContext &C) const {1035  ProgramStateRef State = C.getState();1036  SVal StreamVal = getStreamArg(Desc, Call);1037  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,1038                              State);1039  if (!State)1040    return;1041  State = ensureStreamOpened(StreamVal, C, State);1042  if (!State)1043    return;1044  State = ensureNoFilePositionIndeterminate(StreamVal, C, State);1045  if (!State)1046    return;1047 1048  SymbolRef Sym = StreamVal.getAsSymbol();1049  if (Sym && State->get<StreamMap>(Sym)) {1050    const StreamState *SS = State->get<StreamMap>(Sym);1051    if (SS->ErrorState & ErrorFEof)1052      reportFEofWarning(Sym, C, State);1053  } else {1054    C.addTransition(State);1055  }1056}1057 1058void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call,1059                             CheckerContext &C) const {1060  ProgramStateRef State = C.getState();1061  SVal StreamVal = getStreamArg(Desc, Call);1062  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,1063                              State);1064  if (!State)1065    return;1066  State = ensureStreamOpened(StreamVal, C, State);1067  if (!State)1068    return;1069  State = ensureNoFilePositionIndeterminate(StreamVal, C, State);1070  if (!State)1071    return;1072 1073  C.addTransition(State);1074}1075 1076static QualType getPointeeType(const MemRegion *R) {1077  if (!R)1078    return {};1079  if (const auto *ER = dyn_cast<ElementRegion>(R))1080    return ER->getElementType();1081  if (const auto *TR = dyn_cast<TypedValueRegion>(R))1082    return TR->getValueType();1083  if (const auto *SR = dyn_cast<SymbolicRegion>(R))1084    return SR->getPointeeStaticType();1085  return {};1086}1087 1088static std::optional<NonLoc> getStartIndex(SValBuilder &SVB,1089                                           const MemRegion *R) {1090  if (!R)1091    return std::nullopt;1092 1093  auto Zero = [&SVB] {1094    BasicValueFactory &BVF = SVB.getBasicValueFactory();1095    return nonloc::ConcreteInt(BVF.getIntValue(0, /*isUnsigned=*/false));1096  };1097 1098  if (const auto *ER = dyn_cast<ElementRegion>(R))1099    return ER->getIndex();1100  if (isa<TypedValueRegion>(R))1101    return Zero();1102  if (isa<SymbolicRegion>(R))1103    return Zero();1104  return std::nullopt;1105}1106 1107static ProgramStateRef1108tryToInvalidateFReadBufferByElements(ProgramStateRef State, CheckerContext &C,1109                                     const CallEvent &Call, NonLoc SizeVal,1110                                     NonLoc NMembVal) {1111  // Try to invalidate the individual elements.1112  const auto *Buffer =1113      dyn_cast_or_null<SubRegion>(Call.getArgSVal(0).getAsRegion());1114 1115  const ASTContext &Ctx = C.getASTContext();1116  QualType ElemTy = getPointeeType(Buffer);1117  std::optional<SVal> StartElementIndex =1118      getStartIndex(C.getSValBuilder(), Buffer);1119 1120  // Drop the outermost ElementRegion to get the buffer.1121  if (const auto *ER = dyn_cast_or_null<ElementRegion>(Buffer))1122    Buffer = dyn_cast<SubRegion>(ER->getSuperRegion());1123 1124  std::optional<int64_t> CountVal = getKnownValue(State, NMembVal);1125  std::optional<int64_t> Size = getKnownValue(State, SizeVal);1126  std::optional<int64_t> StartIndexVal =1127      getKnownValue(State, StartElementIndex.value_or(UnknownVal()));1128 1129  if (!ElemTy.isNull() && CountVal && Size && StartIndexVal) {1130    int64_t NumBytesRead = Size.value() * CountVal.value();1131    int64_t ElemSizeInChars = Ctx.getTypeSizeInChars(ElemTy).getQuantity();1132    if (ElemSizeInChars == 0 || NumBytesRead < 0)1133      return nullptr;1134 1135    bool IncompleteLastElement = (NumBytesRead % ElemSizeInChars) != 0;1136    int64_t NumCompleteOrIncompleteElementsRead =1137        NumBytesRead / ElemSizeInChars + IncompleteLastElement;1138 1139    constexpr int MaxInvalidatedElementsLimit = 64;1140    if (NumCompleteOrIncompleteElementsRead <= MaxInvalidatedElementsLimit) {1141      return escapeByStartIndexAndCount(State, Call, C.blockCount(), Buffer,1142                                        ElemTy, *StartIndexVal,1143                                        NumCompleteOrIncompleteElementsRead);1144    }1145  }1146  return nullptr;1147}1148 1149void StreamChecker::evalFreadFwrite(const FnDescription *Desc,1150                                    const CallEvent &Call, CheckerContext &C,1151                                    bool IsFread) const {1152  ProgramStateRef State = C.getState();1153  StreamOperationEvaluator E(C);1154  if (!E.Init(Desc, Call, C, State))1155    return;1156 1157  std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();1158  if (!SizeVal)1159    return;1160  std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();1161  if (!NMembVal)1162    return;1163 1164  // C'99 standard, §7.19.8.1.3, the return value of fread:1165  // The fread function returns the number of elements successfully read, which1166  // may be less than nmemb if a read error or end-of-file is encountered. If1167  // size or nmemb is zero, fread returns zero and the contents of the array and1168  // the state of the stream remain unchanged.1169  if (State->isNull(*SizeVal).isConstrainedTrue() ||1170      State->isNull(*NMembVal).isConstrainedTrue()) {1171    // This is the "size or nmemb is zero" case.1172    // Just return 0, do nothing more (not clear the error flags).1173    C.addTransition(E.bindReturnValue(State, C, 0));1174    return;1175  }1176 1177  // At read, invalidate the buffer in any case of error or success,1178  // except if EOF was already present.1179  if (IsFread && !E.isStreamEof()) {1180    // Try to invalidate the individual elements.1181    // Otherwise just fall back to invalidating the whole buffer.1182    ProgramStateRef InvalidatedState = tryToInvalidateFReadBufferByElements(1183        State, C, Call, *SizeVal, *NMembVal);1184    State =1185        InvalidatedState ? InvalidatedState : escapeArgs(State, C, Call, {0});1186  }1187 1188  // Generate a transition for the success state.1189  // If we know the state to be FEOF at fread, do not add a success state.1190  if (!IsFread || !E.isStreamEof()) {1191    ProgramStateRef StateNotFailed =1192        State->BindExpr(E.CE, C.getLocationContext(), *NMembVal);1193    StateNotFailed =1194        E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1195    C.addTransition(StateNotFailed);1196  }1197 1198  // Add transition for the failed state.1199  // At write, add failure case only if "pedantic mode" is on.1200  if (!IsFread && !PedanticMode)1201    return;1202 1203  NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1204  ProgramStateRef StateFailed =1205      State->BindExpr(E.CE, C.getLocationContext(), RetVal);1206  StateFailed = E.assumeBinOpNN(StateFailed, BO_LT, RetVal, *NMembVal);1207  if (!StateFailed)1208    return;1209 1210  StreamErrorState NewES;1211  if (IsFread)1212    NewES = E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;1213  else1214    NewES = ErrorFError;1215  // If a (non-EOF) error occurs, the resulting value of the file position1216  // indicator for the stream is indeterminate.1217  StateFailed = E.setStreamState(1218      StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));1219  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1220}1221 1222void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call,1223                              CheckerContext &C, bool SingleChar) const {1224  // `fgetc` returns the read character on success, otherwise returns EOF.1225  // `fgets` returns the read buffer address on success, otherwise returns NULL.1226 1227  ProgramStateRef State = C.getState();1228  StreamOperationEvaluator E(C);1229  if (!E.Init(Desc, Call, C, State))1230    return;1231 1232  if (!E.isStreamEof()) {1233    // If there was already EOF, assume that read buffer is not changed.1234    // Otherwise it may change at success or failure.1235    State = escapeArgs(State, C, Call, {0});1236    if (SingleChar) {1237      // Generate a transition for the success state of `fgetc`.1238      NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1239      ProgramStateRef StateNotFailed =1240          State->BindExpr(E.CE, C.getLocationContext(), RetVal);1241      // The returned 'unsigned char' of `fgetc` is converted to 'int',1242      // so we need to check if it is in range [0, 255].1243      StateNotFailed = StateNotFailed->assumeInclusiveRange(1244          RetVal,1245          E.SVB.getBasicValueFactory().getValue(0, E.ACtx.UnsignedCharTy),1246          E.SVB.getBasicValueFactory().getMaxValue(E.ACtx.UnsignedCharTy),1247          true);1248      if (!StateNotFailed)1249        return;1250      C.addTransition(StateNotFailed);1251    } else {1252      // Generate a transition for the success state of `fgets`.1253      std::optional<DefinedSVal> GetBuf =1254          Call.getArgSVal(0).getAs<DefinedSVal>();1255      if (!GetBuf)1256        return;1257      ProgramStateRef StateNotFailed =1258          State->BindExpr(E.CE, C.getLocationContext(), *GetBuf);1259      StateNotFailed =1260          E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1261      C.addTransition(StateNotFailed);1262    }1263  }1264 1265  // Add transition for the failed state.1266  ProgramStateRef StateFailed;1267  if (SingleChar)1268    StateFailed = E.bindReturnValue(State, C, *EofVal);1269  else1270    StateFailed = E.bindNullReturnValue(State, C);1271 1272  // If a (non-EOF) error occurs, the resulting value of the file position1273  // indicator for the stream is indeterminate.1274  StreamErrorState NewES =1275      E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;1276  StateFailed = E.setStreamState(1277      StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));1278  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1279}1280 1281void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,1282                              CheckerContext &C, bool IsSingleChar) const {1283  // `fputc` returns the written character on success, otherwise returns EOF.1284  // `fputs` returns a nonnegative value on success, otherwise returns EOF.1285 1286  ProgramStateRef State = C.getState();1287  StreamOperationEvaluator E(C);1288  if (!E.Init(Desc, Call, C, State))1289    return;1290 1291  if (IsSingleChar) {1292    // Generate a transition for the success state of `fputc`.1293    std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();1294    if (!PutVal)1295      return;1296    ProgramStateRef StateNotFailed =1297        State->BindExpr(E.CE, C.getLocationContext(), *PutVal);1298    StateNotFailed =1299        E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1300    C.addTransition(StateNotFailed);1301  } else {1302    // Generate a transition for the success state of `fputs`.1303    NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1304    ProgramStateRef StateNotFailed =1305        State->BindExpr(E.CE, C.getLocationContext(), RetVal);1306    StateNotFailed =1307        E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));1308    if (!StateNotFailed)1309      return;1310    StateNotFailed =1311        E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1312    C.addTransition(StateNotFailed);1313  }1314 1315  if (!PedanticMode)1316    return;1317 1318  // Add transition for the failed state. The resulting value of the file1319  // position indicator for the stream is indeterminate.1320  ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);1321  StateFailed = E.setStreamState(1322      StateFailed, StreamState::getOpened(Desc, ErrorFError, true));1323  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1324}1325 1326void StreamChecker::evalFprintf(const FnDescription *Desc,1327                                const CallEvent &Call,1328                                CheckerContext &C) const {1329  if (Call.getNumArgs() < 2)1330    return;1331 1332  ProgramStateRef State = C.getState();1333  StreamOperationEvaluator E(C);1334  if (!E.Init(Desc, Call, C, State))1335    return;1336 1337  NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1338  State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);1339  auto Cond =1340      E.SVB1341          .evalBinOp(State, BO_GE, RetVal, E.SVB.makeZeroVal(E.ACtx.IntTy),1342                     E.SVB.getConditionType())1343          .getAs<DefinedOrUnknownSVal>();1344  if (!Cond)1345    return;1346  ProgramStateRef StateNotFailed, StateFailed;1347  std::tie(StateNotFailed, StateFailed) = State->assume(*Cond);1348 1349  StateNotFailed =1350      E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1351  C.addTransition(StateNotFailed);1352 1353  if (!PedanticMode)1354    return;1355 1356  // Add transition for the failed state. The resulting value of the file1357  // position indicator for the stream is indeterminate.1358  StateFailed = E.setStreamState(1359      StateFailed, StreamState::getOpened(Desc, ErrorFError, true));1360  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1361}1362 1363void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,1364                               CheckerContext &C) const {1365  if (Call.getNumArgs() < 2)1366    return;1367 1368  ProgramStateRef State = C.getState();1369  StreamOperationEvaluator E(C);1370  if (!E.Init(Desc, Call, C, State))1371    return;1372 1373  // Add the success state.1374  // In this context "success" means there is not an EOF or other read error1375  // before any item is matched in 'fscanf'. But there may be match failure,1376  // therefore return value can be 0 or greater.1377  // It is not specified what happens if some items (not all) are matched and1378  // then EOF or read error happens. Now this case is handled like a "success"1379  // case, and no error flags are set on the stream. This is probably not1380  // accurate, and the POSIX documentation does not tell more.1381  if (!E.isStreamEof()) {1382    NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1383    ProgramStateRef StateNotFailed =1384        State->BindExpr(E.CE, C.getLocationContext(), RetVal);1385    StateNotFailed =1386        E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));1387    if (!StateNotFailed)1388      return;1389 1390    if (auto const *Callee = Call.getCalleeIdentifier();1391        !Callee || Callee->getName() != "vfscanf") {1392      SmallVector<unsigned int> EscArgs;1393      for (auto EscArg : llvm::seq(2u, Call.getNumArgs()))1394        EscArgs.push_back(EscArg);1395      StateNotFailed = escapeArgs(StateNotFailed, C, Call, EscArgs);1396    }1397 1398    if (StateNotFailed)1399      C.addTransition(StateNotFailed);1400  }1401 1402  // Add transition for the failed state.1403  // Error occurs if nothing is matched yet and reading the input fails.1404  // Error can be EOF, or other error. At "other error" FERROR or 'errno' can1405  // be set but it is not further specified if all are required to be set.1406  // Documentation does not mention, but file position will be set to1407  // indeterminate similarly as at 'fread'.1408  ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);1409  StreamErrorState NewES =1410      E.isStreamEof() ? ErrorFEof : ErrorNone | ErrorFEof | ErrorFError;1411  StateFailed = E.setStreamState(1412      StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));1413  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1414}1415 1416void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,1417                               CheckerContext &C) const {1418  ProgramStateRef State = C.getState();1419  StreamOperationEvaluator E(C);1420  if (!E.Init(Desc, Call, C, State))1421    return;1422 1423  // Generate a transition for the success state.1424  std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();1425  if (!PutVal)1426    return;1427  ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, *PutVal);1428  StateNotFailed =1429      E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1430  C.addTransition(StateNotFailed);1431 1432  // Add transition for the failed state.1433  // Failure of 'ungetc' does not result in feof or ferror state.1434  // If the PutVal has value of EofVal the function should "fail", but this is1435  // the same transition as the success state.1436  // In this case only one state transition is added by the analyzer (the two1437  // new states may be similar).1438  ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);1439  StateFailed = E.setStreamState(StateFailed, StreamState::getOpened(Desc));1440  C.addTransition(StateFailed);1441}1442 1443void StreamChecker::evalGetdelim(const FnDescription *Desc,1444                                 const CallEvent &Call,1445                                 CheckerContext &C) const {1446  ProgramStateRef State = C.getState();1447  StreamOperationEvaluator E(C);1448  if (!E.Init(Desc, Call, C, State))1449    return;1450 1451  // Upon successful completion, the getline() and getdelim() functions shall1452  // return the number of bytes written into the buffer.1453  // If the end-of-file indicator for the stream is set, the function shall1454  // return -1.1455  // If an error occurs, the function shall return -1 and set 'errno'.1456 1457  if (!E.isStreamEof()) {1458    // Escape buffer and size (may change by the call).1459    // May happen even at error (partial read?).1460    State = escapeArgs(State, C, Call, {0, 1});1461 1462    // Add transition for the successful state.1463    NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1464    ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, RetVal);1465    StateNotFailed =1466        E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));1467 1468    // On success, a buffer is allocated.1469    auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State);1470    if (NewLinePtr && isa<DefinedOrUnknownSVal>(*NewLinePtr))1471      StateNotFailed = StateNotFailed->assume(1472          NewLinePtr->castAs<DefinedOrUnknownSVal>(), true);1473 1474    // The buffer size `*n` must be enough to hold the whole line, and1475    // greater than the return value, since it has to account for '\0'.1476    SVal SizePtrSval = Call.getArgSVal(1);1477    auto NVal = getPointeeVal(SizePtrSval, State);1478    if (NVal && isa<NonLoc>(*NVal)) {1479      StateNotFailed = E.assumeBinOpNN(StateNotFailed, BO_GT,1480                                       NVal->castAs<NonLoc>(), RetVal);1481      StateNotFailed = E.bindReturnValue(StateNotFailed, C, RetVal);1482    }1483    if (!StateNotFailed)1484      return;1485    C.addTransition(StateNotFailed);1486  }1487 1488  // Add transition for the failed state.1489  // If a (non-EOF) error occurs, the resulting value of the file position1490  // indicator for the stream is indeterminate.1491  ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);1492  StreamErrorState NewES =1493      E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;1494  StateFailed = E.setStreamState(1495      StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));1496  // On failure, the content of the buffer is undefined.1497  if (auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State))1498    StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),1499                                       C.getLocationContext());1500  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1501}1502 1503void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,1504                             CheckerContext &C) const {1505  ProgramStateRef State = C.getState();1506  SVal StreamVal = getStreamArg(Desc, Call);1507  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,1508                              State);1509  if (!State)1510    return;1511  State = ensureStreamOpened(StreamVal, C, State);1512  if (!State)1513    return;1514  State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);1515  if (!State)1516    return;1517 1518  C.addTransition(State);1519}1520 1521void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,1522                              CheckerContext &C) const {1523  ProgramStateRef State = C.getState();1524  StreamOperationEvaluator E(C);1525  if (!E.Init(Desc, Call, C, State))1526    return;1527 1528  // Add success state.1529  ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, 0);1530  // No failure: Reset the state to opened with no error.1531  StateNotFailed =1532      E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));1533  C.addTransition(StateNotFailed);1534 1535  if (!PedanticMode)1536    return;1537 1538  // Add failure state.1539  // At error it is possible that fseek fails but sets none of the error flags.1540  // If fseek failed, assume that the file position becomes indeterminate in any1541  // case.1542  // It is allowed to set the position beyond the end of the file. EOF error1543  // should not occur.1544  ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);1545  StateFailed = E.setStreamState(1546      StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));1547  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1548}1549 1550void StreamChecker::evalFgetpos(const FnDescription *Desc,1551                                const CallEvent &Call,1552                                CheckerContext &C) const {1553  ProgramStateRef State = C.getState();1554  StreamOperationEvaluator E(C);1555  if (!E.Init(Desc, Call, C, State))1556    return;1557 1558  ProgramStateRef StateNotFailed, StateFailed;1559  std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);1560  StateNotFailed = escapeArgs(StateNotFailed, C, Call, {1});1561 1562  // This function does not affect the stream state.1563  // Still we add success and failure state with the appropriate return value.1564  // StdLibraryFunctionsChecker can change these states (set the 'errno' state).1565  C.addTransition(StateNotFailed);1566  C.addTransition(StateFailed);1567}1568 1569void StreamChecker::evalFsetpos(const FnDescription *Desc,1570                                const CallEvent &Call,1571                                CheckerContext &C) const {1572  ProgramStateRef State = C.getState();1573  StreamOperationEvaluator E(C);1574  if (!E.Init(Desc, Call, C, State))1575    return;1576 1577  ProgramStateRef StateNotFailed, StateFailed;1578  std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C);1579 1580  StateNotFailed = E.setStreamState(1581      StateNotFailed, StreamState::getOpened(Desc, ErrorNone, false));1582  C.addTransition(StateNotFailed);1583 1584  if (!PedanticMode)1585    return;1586 1587  // At failure ferror could be set.1588  // The standards do not tell what happens with the file position at failure.1589  // But we can assume that it is dangerous to make a next I/O operation after1590  // the position was not set correctly (similar to 'fseek').1591  StateFailed = E.setStreamState(1592      StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));1593 1594  C.addTransition(StateFailed, E.getFailureNoteTag(this, C));1595}1596 1597void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,1598                              CheckerContext &C) const {1599  ProgramStateRef State = C.getState();1600  StreamOperationEvaluator E(C);1601  if (!E.Init(Desc, Call, C, State))1602    return;1603 1604  NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1605  ProgramStateRef StateNotFailed =1606      State->BindExpr(E.CE, C.getLocationContext(), RetVal);1607  StateNotFailed =1608      E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));1609  if (!StateNotFailed)1610    return;1611 1612  ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);1613 1614  // This function does not affect the stream state.1615  // Still we add success and failure state with the appropriate return value.1616  // StdLibraryFunctionsChecker can change these states (set the 'errno' state).1617  C.addTransition(StateNotFailed);1618  C.addTransition(StateFailed);1619}1620 1621void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,1622                               CheckerContext &C) const {1623  ProgramStateRef State = C.getState();1624  StreamOperationEvaluator E(C);1625  if (!E.Init(Desc, Call, C, State))1626    return;1627 1628  State =1629      E.setStreamState(State, StreamState::getOpened(Desc, ErrorNone, false));1630  C.addTransition(State);1631}1632 1633void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,1634                              CheckerContext &C) const {1635  ProgramStateRef State = C.getState();1636  SVal StreamVal = getStreamArg(Desc, Call);1637  std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();1638  if (!Stream)1639    return;1640 1641  ProgramStateRef StateNotNull, StateNull;1642  std::tie(StateNotNull, StateNull) =1643      C.getConstraintManager().assumeDual(State, *Stream);1644  if (StateNotNull && !StateNull)1645    ensureStreamOpened(StreamVal, C, StateNotNull);1646}1647 1648void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,1649                               CheckerContext &C) const {1650  ProgramStateRef State = C.getState();1651  SVal StreamVal = getStreamArg(Desc, Call);1652  std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();1653  if (!Stream)1654    return;1655 1656  // Skip if the stream can be both NULL and non-NULL.1657  ProgramStateRef StateNotNull, StateNull;1658  std::tie(StateNotNull, StateNull) =1659      C.getConstraintManager().assumeDual(State, *Stream);1660  if (StateNotNull && StateNull)1661    return;1662  if (StateNotNull && !StateNull)1663    State = StateNotNull;1664  else1665    State = StateNull;1666 1667  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());1668  if (!CE)1669    return;1670 1671  // `fflush` returns EOF on failure, otherwise returns 0.1672  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);1673  ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);1674 1675  // Clear error states if `fflush` returns 0, but retain their EOF flags.1676  auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym,1677                                                       const StreamState *SS) {1678    if (SS->ErrorState & ErrorFError) {1679      StreamErrorState NewES =1680          (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;1681      StreamState NewSS = StreamState::getOpened(Desc, NewES, false);1682      StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);1683    }1684  };1685 1686  if (StateNotNull && !StateNull) {1687    // Skip if the input stream's state is unknown, open-failed or closed.1688    if (SymbolRef StreamSym = StreamVal.getAsSymbol()) {1689      const StreamState *SS = State->get<StreamMap>(StreamSym);1690      if (SS) {1691        assert(SS->isOpened() && "Stream is expected to be opened");1692        ClearErrorInNotFailed(StreamSym, SS);1693      } else1694        return;1695    }1696  } else {1697    // Clear error states for all streams.1698    const StreamMapTy &Map = StateNotFailed->get<StreamMap>();1699    for (const auto &I : Map) {1700      SymbolRef Sym = I.first;1701      const StreamState &SS = I.second;1702      if (SS.isOpened())1703        ClearErrorInNotFailed(Sym, &SS);1704    }1705  }1706 1707  C.addTransition(StateNotFailed);1708  C.addTransition(StateFailed);1709}1710 1711void StreamChecker::evalClearerr(const FnDescription *Desc,1712                                 const CallEvent &Call,1713                                 CheckerContext &C) const {1714  ProgramStateRef State = C.getState();1715  StreamOperationEvaluator E(C);1716  if (!E.Init(Desc, Call, C, State))1717    return;1718 1719  // FilePositionIndeterminate is not cleared.1720  State = E.setStreamState(1721      State,1722      StreamState::getOpened(Desc, ErrorNone, E.SS->FilePositionIndeterminate));1723  C.addTransition(State);1724}1725 1726void StreamChecker::evalFeofFerror(const FnDescription *Desc,1727                                   const CallEvent &Call, CheckerContext &C,1728                                   const StreamErrorState &ErrorKind) const {1729  ProgramStateRef State = C.getState();1730  StreamOperationEvaluator E(C);1731  if (!E.Init(Desc, Call, C, State))1732    return;1733 1734  if (E.SS->ErrorState & ErrorKind) {1735    // Execution path with error of ErrorKind.1736    // Function returns true.1737    // From now on it is the only one error state.1738    ProgramStateRef TrueState =1739        bindAndAssumeTrue(State, C, E.CE, E.Elem.value());1740    C.addTransition(E.setStreamState(1741        TrueState, StreamState::getOpened(Desc, ErrorKind,1742                                          E.SS->FilePositionIndeterminate &&1743                                              !ErrorKind.isFEof())));1744  }1745  if (StreamErrorState NewES = E.SS->ErrorState & (~ErrorKind)) {1746    // Execution path(s) with ErrorKind not set.1747    // Function returns false.1748    // New error state is everything before minus ErrorKind.1749    ProgramStateRef FalseState = E.bindReturnValue(State, C, 0);1750    C.addTransition(E.setStreamState(1751        FalseState,1752        StreamState::getOpened(1753            Desc, NewES, E.SS->FilePositionIndeterminate && !NewES.isFEof())));1754  }1755}1756 1757void StreamChecker::evalFileno(const FnDescription *Desc, const CallEvent &Call,1758                               CheckerContext &C) const {1759  // Fileno should fail only if the passed pointer is invalid.1760  // Some of the preconditions are checked already in preDefault.1761  // Here we can assume that the operation does not fail, because if we1762  // introduced a separate branch where fileno() returns -1, then it would cause1763  // many unexpected and unwanted warnings in situations where fileno() is1764  // called on valid streams.1765  // The stream error states are not modified by 'fileno', and 'errno' is also1766  // left unchanged (so this evalCall does not invalidate it, but we have a1767  // custom evalCall instead of the default that would invalidate it).1768  ProgramStateRef State = C.getState();1769  StreamOperationEvaluator E(C);1770  if (!E.Init(Desc, Call, C, State))1771    return;1772 1773  NonLoc RetVal = makeRetVal(C, E.Elem.value()).castAs<NonLoc>();1774  State = State->BindExpr(E.CE, C.getLocationContext(), RetVal);1775  State = E.assumeBinOpNN(State, BO_GE, RetVal, E.getZeroVal(Call));1776  if (!State)1777    return;1778 1779  C.addTransition(State);1780}1781 1782void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,1783                               CheckerContext &C) const {1784  ProgramStateRef State = C.getState();1785  SVal StreamVal = getStreamArg(Desc, Call);1786  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,1787                              State);1788  if (!State)1789    return;1790  State = ensureStreamOpened(StreamVal, C, State);1791  if (!State)1792    return;1793 1794  C.addTransition(State);1795}1796 1797void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,1798                                      const CallEvent &Call, CheckerContext &C,1799                                      const StreamErrorState &ErrorKind,1800                                      bool Indeterminate) const {1801  ProgramStateRef State = C.getState();1802  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();1803  assert(StreamSym && "Operation not permitted on non-symbolic stream value.");1804  const StreamState *SS = State->get<StreamMap>(StreamSym);1805  assert(SS && "Stream should be tracked by the checker.");1806  State = State->set<StreamMap>(1807      StreamSym,1808      StreamState::getOpened(SS->LastOperation, ErrorKind, Indeterminate));1809  C.addTransition(State);1810}1811 1812ProgramStateRef1813StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,1814                                   CheckerContext &C,1815                                   ProgramStateRef State) const {1816  auto Stream = StreamVal.getAs<DefinedSVal>();1817  if (!Stream)1818    return State;1819 1820  ConstraintManager &CM = C.getConstraintManager();1821 1822  ProgramStateRef StateNotNull, StateNull;1823  std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream);1824 1825  if (!StateNotNull && StateNull) {1826    if (ExplodedNode *N = C.generateErrorNode(StateNull)) {1827      auto R = std::make_unique<PathSensitiveBugReport>(1828          BT_FileNull, "Stream pointer might be NULL.", N);1829      if (StreamE)1830        bugreporter::trackExpressionValue(N, StreamE, *R);1831      C.emitReport(std::move(R));1832    }1833    return nullptr;1834  }1835 1836  return StateNotNull;1837}1838 1839namespace {1840class StreamClosedVisitor final : public BugReporterVisitor {1841  const SymbolRef StreamSym;1842  bool Satisfied = false;1843 1844public:1845  explicit StreamClosedVisitor(SymbolRef StreamSym) : StreamSym(StreamSym) {}1846 1847  static void *getTag() {1848    static int Tag = 0;1849    return &Tag;1850  }1851 1852  void Profile(llvm::FoldingSetNodeID &ID) const override {1853    ID.AddPointer(getTag());1854    ID.AddPointer(StreamSym);1855  }1856 1857  PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,1858                                   BugReporterContext &BRC,1859                                   PathSensitiveBugReport &BR) override {1860    if (Satisfied)1861      return nullptr;1862    const StreamState *PredSS =1863        N->getFirstPred()->getState()->get<StreamMap>(StreamSym);1864    if (PredSS && PredSS->isClosed())1865      return nullptr;1866 1867    const Stmt *S = N->getStmtForDiagnostics();1868    if (!S)1869      return nullptr;1870    Satisfied = true;1871    PathDiagnosticLocation Pos(S, BRC.getSourceManager(),1872                               N->getLocationContext());1873    llvm::StringLiteral Msg = "Stream is closed here";1874    return std::make_shared<PathDiagnosticEventPiece>(Pos, Msg);1875  }1876};1877} // namespace1878 1879ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,1880                                                  CheckerContext &C,1881                                                  ProgramStateRef State) const {1882  SymbolRef Sym = StreamVal.getAsSymbol();1883  if (!Sym)1884    return State;1885 1886  const StreamState *SS = State->get<StreamMap>(Sym);1887  if (!SS)1888    return State;1889 1890  if (SS->isClosed()) {1891    // Using a stream pointer after 'fclose' causes undefined behavior1892    // according to cppreference.com .1893    if (ExplodedNode *N = C.generateErrorNode()) {1894      auto R = std::make_unique<PathSensitiveBugReport>(1895          BT_UseAfterClose, "Use of a stream that might be already closed", N);1896      R->addVisitor<StreamClosedVisitor>(Sym);1897      C.emitReport(std::move(R));1898      return nullptr;1899    }1900 1901    return State;1902  }1903 1904  if (SS->isOpenFailed()) {1905    // Using a stream that has failed to open is likely to cause problems.1906    // This should usually not occur because stream pointer is NULL.1907    // But freopen can cause a state when stream pointer remains non-null but1908    // failed to open.1909    ExplodedNode *N = C.generateErrorNode();1910    if (N) {1911      C.emitReport(std::make_unique<PathSensitiveBugReport>(1912          BT_UseAfterOpenFailed,1913          "Stream might be invalid after "1914          "(re-)opening it has failed. "1915          "Can cause undefined behaviour.",1916          N));1917      return nullptr;1918    }1919  }1920 1921  return State;1922}1923 1924ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(1925    SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {1926  static const char *BugMessage =1927      "File position of the stream might be 'indeterminate' "1928      "after a failed operation. "1929      "Can cause undefined behavior.";1930 1931  SymbolRef Sym = StreamVal.getAsSymbol();1932  if (!Sym)1933    return State;1934 1935  const StreamState *SS = State->get<StreamMap>(Sym);1936  if (!SS)1937    return State;1938 1939  assert(SS->isOpened() && "First ensure that stream is opened.");1940 1941  if (SS->FilePositionIndeterminate) {1942    if (SS->ErrorState & ErrorFEof) {1943      // The error is unknown but may be FEOF.1944      // Continue analysis with the FEOF error state.1945      // Report warning because the other possible error states.1946      ExplodedNode *N = C.generateNonFatalErrorNode(State);1947      if (!N)1948        return nullptr;1949 1950      auto R = std::make_unique<PathSensitiveBugReport>(1951          BT_IndeterminatePosition, BugMessage, N);1952      R->markInteresting(Sym);1953      C.emitReport(std::move(R));1954      return State->set<StreamMap>(1955          Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));1956    }1957 1958    // Known or unknown error state without FEOF possible.1959    // Stop analysis, report error.1960    if (ExplodedNode *N = C.generateErrorNode(State)) {1961      auto R = std::make_unique<PathSensitiveBugReport>(1962          BT_IndeterminatePosition, BugMessage, N);1963      R->markInteresting(Sym);1964      C.emitReport(std::move(R));1965    }1966 1967    return nullptr;1968  }1969 1970  return State;1971}1972 1973ProgramStateRef1974StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,1975                                        ProgramStateRef State) const {1976  std::optional<nonloc::ConcreteInt> CI =1977      WhenceVal.getAs<nonloc::ConcreteInt>();1978  if (!CI)1979    return State;1980 1981  int64_t X = CI->getValue()->getSExtValue();1982  if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)1983    return State;1984 1985  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {1986    C.emitReport(std::make_unique<PathSensitiveBugReport>(1987        BT_IllegalWhence,1988        "The whence argument to fseek() should be "1989        "SEEK_SET, SEEK_END, or SEEK_CUR.",1990        N));1991    return nullptr;1992  }1993 1994  return State;1995}1996 1997void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,1998                                      ProgramStateRef State) const {1999  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {2000    auto R = std::make_unique<PathSensitiveBugReport>(2001        BT_StreamEof,2002        "Read function called when stream is in EOF state. "2003        "Function has no effect.",2004        N);2005    R->markInteresting(StreamSym);2006    C.emitReport(std::move(R));2007    return;2008  }2009  C.addTransition(State);2010}2011 2012ExplodedNode *2013StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,2014                           CheckerContext &C, ExplodedNode *Pred) const {2015  ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);2016  if (!Err)2017    return Pred;2018 2019  for (SymbolRef LeakSym : LeakedSyms) {2020    // Resource leaks can result in multiple warning that describe the same kind2021    // of programming error:2022    //  void f() {2023    //    FILE *F = fopen("a.txt");2024    //    if (rand()) // state split2025    //      return; // warning2026    //  } // warning2027    // While this isn't necessarily true (leaking the same stream could result2028    // from a different kinds of errors), the reduction in redundant reports2029    // makes this a worthwhile heuristic.2030    // FIXME: Add a checker option to turn this uniqueing feature off.2031    const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);2032    assert(StreamOpenNode && "Could not find place of stream opening.");2033 2034    PathDiagnosticLocation LocUsedForUniqueing;2035    if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics())2036      LocUsedForUniqueing = PathDiagnosticLocation::createBegin(2037          StreamStmt, C.getSourceManager(),2038          StreamOpenNode->getLocationContext());2039 2040    std::unique_ptr<PathSensitiveBugReport> R =2041        std::make_unique<PathSensitiveBugReport>(2042            BT_ResourceLeak,2043            "Opened stream never closed. Potential resource leak.", Err,2044            LocUsedForUniqueing,2045            StreamOpenNode->getLocationContext()->getDecl());2046    R->markInteresting(LeakSym);2047    R->addVisitor<NoStreamStateChangeVisitor>(LeakSym, this);2048    C.emitReport(std::move(R));2049  }2050 2051  return Err;2052}2053 2054void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,2055                                     CheckerContext &C) const {2056  ProgramStateRef State = C.getState();2057 2058  llvm::SmallVector<SymbolRef, 2> LeakedSyms;2059 2060  const StreamMapTy &Map = State->get<StreamMap>();2061  for (const auto &I : Map) {2062    SymbolRef Sym = I.first;2063    const StreamState &SS = I.second;2064    if (!SymReaper.isDead(Sym))2065      continue;2066    if (SS.isOpened())2067      LeakedSyms.push_back(Sym);2068    State = State->remove<StreamMap>(Sym);2069  }2070 2071  ExplodedNode *N = C.getPredecessor();2072  if (!LeakedSyms.empty())2073    N = reportLeaks(LeakedSyms, C, N);2074 2075  C.addTransition(State, N);2076}2077 2078ProgramStateRef StreamChecker::checkPointerEscape(2079    ProgramStateRef State, const InvalidatedSymbols &Escaped,2080    const CallEvent *Call, PointerEscapeKind Kind) const {2081  // Check for file-handling system call that is not handled by the checker.2082  // FIXME: The checker should be updated to handle all system calls that take2083  // 'FILE*' argument. These are now ignored.2084  if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())2085    return State;2086 2087  for (SymbolRef Sym : Escaped) {2088    // The symbol escaped.2089    // From now the stream can be manipulated in unknown way to the checker,2090    // it is not possible to handle it any more.2091    // Optimistically, assume that the corresponding file handle will be closed2092    // somewhere else.2093    // Remove symbol from state so the following stream calls on this symbol are2094    // not handled by the checker.2095    State = State->remove<StreamMap>(Sym);2096  }2097  return State;2098}2099 2100static const VarDecl *2101getGlobalStreamPointerByName(const TranslationUnitDecl *TU, StringRef VarName) {2102  ASTContext &Ctx = TU->getASTContext();2103  const auto &SM = Ctx.getSourceManager();2104  const QualType FileTy = Ctx.getFILEType();2105 2106  if (FileTy.isNull())2107    return nullptr;2108 2109  const QualType FilePtrTy = Ctx.getPointerType(FileTy).getCanonicalType();2110 2111  auto LookupRes = TU->lookup(&Ctx.Idents.get(VarName));2112  for (const Decl *D : LookupRes) {2113    if (auto *VD = dyn_cast_or_null<VarDecl>(D)) {2114      if (SM.isInSystemHeader(VD->getLocation()) && VD->hasExternalStorage() &&2115          VD->getType().getCanonicalType() == FilePtrTy) {2116        return VD;2117      }2118    }2119  }2120  return nullptr;2121}2122 2123void StreamChecker::checkASTDecl(const TranslationUnitDecl *TU,2124                                 AnalysisManager &Mgr, BugReporter &) const {2125  StdinDecl = getGlobalStreamPointerByName(TU, "stdin");2126  StdoutDecl = getGlobalStreamPointerByName(TU, "stdout");2127  StderrDecl = getGlobalStreamPointerByName(TU, "stderr");2128  VaListType = TU->getASTContext().getBuiltinVaListType().getCanonicalType();2129  initMacroValues(Mgr.getPreprocessor());2130}2131 2132//===----------------------------------------------------------------------===//2133// Checker registration.2134//===----------------------------------------------------------------------===//2135 2136void ento::registerStreamChecker(CheckerManager &Mgr) {2137  auto *Checker = Mgr.registerChecker<StreamChecker>();2138  Checker->PedanticMode =2139      Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "Pedantic");2140}2141 2142bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {2143  return true;2144}2145 2146void ento::registerStreamTesterChecker(CheckerManager &Mgr) {2147  auto *Checker = Mgr.getChecker<StreamChecker>();2148  Checker->TestMode = true;2149}2150 2151bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {2152  return true;2153}2154