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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines checkers that model and check stream handling functions.
10//
11//===----------------------------------------------------------------------===//
12
13#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15#include "clang/StaticAnalyzer/Core/Checker.h"
16#include "clang/StaticAnalyzer/Core/CheckerManager.h"
17#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
18#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
24#include <functional>
25#include <optional>
26
27using namespace clang;
28using namespace ento;
29using namespace std::placeholders;
30
31//===----------------------------------------------------------------------===//
32// Definition of state data structures.
33//===----------------------------------------------------------------------===//
34
35namespace {
36
37struct FnDescription;
38
39/// State of the stream error flags.
40/// Sometimes it is not known to the checker what error flags are set.
41/// This is indicated by setting more than one flag to true.
42/// This is an optimization to avoid state splits.
43/// A stream can either be in FEOF or FERROR but not both at the same time.
44/// Multiple flags are set to handle the corresponding states together.
45struct StreamErrorState {
46  /// The stream can be in state where none of the error flags set.
47  bool NoError = true;
48  /// The stream can be in state where the EOF indicator is set.
49  bool FEof = false;
50  /// The stream can be in state where the error indicator is set.
51  bool FError = false;
52
53  bool isNoError() const { return NoError && !FEof && !FError; }
54  bool isFEof() const { return !NoError && FEof && !FError; }
55  bool isFError() const { return !NoError && !FEof && FError; }
56
57  bool operator==(const StreamErrorState &ES) const {
58    return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError;
59  }
60
61  bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); }
62
63  StreamErrorState operator|(const StreamErrorState &E) const {
64    return {NoError || E.NoError, FEof || E.FEof, FError || E.FError};
65  }
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 { return {!NoError, !FEof, !FError}; }
72
73  /// Returns if the StreamErrorState is a valid object.
74  operator bool() const { return NoError || FEof || FError; }
75
76  void Profile(llvm::FoldingSetNodeID &ID) const {
77    ID.AddBoolean(NoError);
78    ID.AddBoolean(FEof);
79    ID.AddBoolean(FError);
80  }
81};
82
83const StreamErrorState ErrorNone{true, false, false};
84const StreamErrorState ErrorFEof{false, true, false};
85const StreamErrorState ErrorFError{false, false, true};
86
87/// Full state information about a stream pointer.
88struct StreamState {
89  /// The last file operation called in the stream.
90  /// Can be nullptr.
91  const FnDescription *LastOperation;
92
93  /// State of a stream symbol.
94  enum KindTy {
95    Opened, /// Stream is opened.
96    Closed, /// Closed stream (an invalid stream pointer after it was closed).
97    OpenFailed /// The last open operation has failed.
98  } State;
99
100  /// State of the error flags.
101  /// Ignored in non-opened stream state but must be NoError.
102  StreamErrorState const ErrorState;
103
104  /// Indicate if the file has an "indeterminate file position indicator".
105  /// This can be set at a failing read or write or seek operation.
106  /// If it is set no more read or write is allowed.
107  /// This value is not dependent on the stream error flags:
108  /// The error flag may be cleared with `clearerr` but the file position
109  /// remains still indeterminate.
110  /// This value applies to all error states in ErrorState except FEOF.
111  /// An EOF+indeterminate state is the same as EOF state.
112  bool const FilePositionIndeterminate = false;
113
114  StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES,
115              bool IsFilePositionIndeterminate)
116      : LastOperation(L), State(S), ErrorState(ES),
117        FilePositionIndeterminate(IsFilePositionIndeterminate) {
118    assert((!ES.isFEof() || !IsFilePositionIndeterminate) &&
119           "FilePositionIndeterminate should be false in FEof case.");
120    assert((State == Opened || ErrorState.isNoError()) &&
121           "ErrorState should be None in non-opened stream state.");
122  }
123
124  bool isOpened() const { return State == Opened; }
125  bool isClosed() const { return State == Closed; }
126  bool isOpenFailed() const { return State == OpenFailed; }
127
128  bool operator==(const StreamState &X) const {
129    // In not opened state error state should always NoError, so comparison
130    // here is no problem.
131    return LastOperation == X.LastOperation && State == X.State &&
132           ErrorState == X.ErrorState &&
133           FilePositionIndeterminate == X.FilePositionIndeterminate;
134  }
135
136  static StreamState getOpened(const FnDescription *L,
137                               const StreamErrorState &ES = ErrorNone,
138                               bool IsFilePositionIndeterminate = false) {
139    return StreamState{L, Opened, ES, IsFilePositionIndeterminate};
140  }
141  static StreamState getClosed(const FnDescription *L) {
142    return StreamState{L, Closed, {}, false};
143  }
144  static StreamState getOpenFailed(const FnDescription *L) {
145    return StreamState{L, OpenFailed, {}, false};
146  }
147
148  void Profile(llvm::FoldingSetNodeID &ID) const {
149    ID.AddPointer(LastOperation);
150    ID.AddInteger(State);
151    ErrorState.Profile(ID);
152    ID.AddBoolean(FilePositionIndeterminate);
153  }
154};
155
156} // namespace
157
158//===----------------------------------------------------------------------===//
159// StreamChecker class and utility functions.
160//===----------------------------------------------------------------------===//
161
162namespace {
163
164class StreamChecker;
165using FnCheck = std::function<void(const StreamChecker *, const FnDescription *,
166                                   const CallEvent &, CheckerContext &)>;
167
168using ArgNoTy = unsigned int;
169static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max();
170
171struct FnDescription {
172  FnCheck PreFn;
173  FnCheck EvalFn;
174  ArgNoTy StreamArgNo;
175};
176
177/// Get the value of the stream argument out of the passed call event.
178/// The call should contain a function that is described by Desc.
179SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) {
180  assert(Desc && Desc->StreamArgNo != ArgNone &&
181         "Try to get a non-existing stream argument.");
182  return Call.getArgSVal(Desc->StreamArgNo);
183}
184
185/// Create a conjured symbol return value for a call expression.
186DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) {
187  assert(CE && "Expecting a call expression.");
188
189  const LocationContext *LCtx = C.getLocationContext();
190  return C.getSValBuilder()
191      .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
192      .castAs<DefinedSVal>();
193}
194
195ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C,
196                                  const CallExpr *CE) {
197  DefinedSVal RetVal = makeRetVal(C, CE);
198  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
199  State = State->assume(RetVal, true);
200  assert(State && "Assumption on new value should not fail.");
201  return State;
202}
203
204ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State,
205                        CheckerContext &C, const CallExpr *CE) {
206  State = State->BindExpr(CE, C.getLocationContext(),
207                          C.getSValBuilder().makeIntVal(Value, CE->getType()));
208  return State;
209}
210
211class StreamChecker : public Checker<check::PreCall, eval::Call,
212                                     check::DeadSymbols, check::PointerEscape> {
213  BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"};
214  BugType BT_UseAfterOpenFailed{this, "Invalid stream",
215                                "Stream handling error"};
216  BugType BT_IndeterminatePosition{this, "Invalid stream state",
217                                   "Stream handling error"};
218  BugType BT_IllegalWhence{this, "Illegal whence argument",
219                           "Stream handling error"};
220  BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"};
221  BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
222                          /*SuppressOnSink =*/true};
223
224public:
225  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
226  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
227  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
228  ProgramStateRef checkPointerEscape(ProgramStateRef State,
229                                     const InvalidatedSymbols &Escaped,
230                                     const CallEvent *Call,
231                                     PointerEscapeKind Kind) const;
232
233  /// If true, evaluate special testing stream functions.
234  bool TestMode = false;
235
236  const BugType *getBT_StreamEof() const { return &BT_StreamEof; }
237
238private:
239  CallDescriptionMap<FnDescription> FnDescriptions = {
240      {{{"fopen"}}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
241      {{{"freopen"}, 3},
242       {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}},
243      {{{"tmpfile"}}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
244      {{{"fclose"}, 1},
245       {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
246      {{{"fread"}, 4},
247       {&StreamChecker::preFread,
248        std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
249      {{{"fwrite"}, 4},
250       {&StreamChecker::preFwrite,
251        std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
252      {{{"fseek"}, 3},
253       {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
254      {{{"ftell"}, 1},
255       {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
256      {{{"rewind"}, 1},
257       {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
258      {{{"fgetpos"}, 2},
259       {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}},
260      {{{"fsetpos"}, 2},
261       {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
262      {{{"clearerr"}, 1},
263       {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
264      {{{"feof"}, 1},
265       {&StreamChecker::preDefault,
266        std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof),
267        0}},
268      {{{"ferror"}, 1},
269       {&StreamChecker::preDefault,
270        std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError),
271        0}},
272      {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}},
273  };
274
275  CallDescriptionMap<FnDescription> FnTestDescriptions = {
276      {{{"StreamTesterChecker_make_feof_stream"}, 1},
277       {nullptr,
278        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof),
279        0}},
280      {{{"StreamTesterChecker_make_ferror_stream"}, 1},
281       {nullptr,
282        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4,
283                  ErrorFError),
284        0}},
285  };
286
287  mutable std::optional<int> EofVal;
288
289  void evalFopen(const FnDescription *Desc, const CallEvent &Call,
290                 CheckerContext &C) const;
291
292  void preFreopen(const FnDescription *Desc, const CallEvent &Call,
293                  CheckerContext &C) const;
294  void evalFreopen(const FnDescription *Desc, const CallEvent &Call,
295                   CheckerContext &C) const;
296
297  void evalFclose(const FnDescription *Desc, const CallEvent &Call,
298                  CheckerContext &C) const;
299
300  void preFread(const FnDescription *Desc, const CallEvent &Call,
301                CheckerContext &C) const;
302
303  void preFwrite(const FnDescription *Desc, const CallEvent &Call,
304                 CheckerContext &C) const;
305
306  void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
307                       CheckerContext &C, bool IsFread) const;
308
309  void preFseek(const FnDescription *Desc, const CallEvent &Call,
310                CheckerContext &C) const;
311  void evalFseek(const FnDescription *Desc, const CallEvent &Call,
312                 CheckerContext &C) const;
313
314  void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
315                   CheckerContext &C) const;
316
317  void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
318                   CheckerContext &C) const;
319
320  void evalFtell(const FnDescription *Desc, const CallEvent &Call,
321                 CheckerContext &C) const;
322
323  void evalRewind(const FnDescription *Desc, const CallEvent &Call,
324                  CheckerContext &C) const;
325
326  void preDefault(const FnDescription *Desc, const CallEvent &Call,
327                  CheckerContext &C) const;
328
329  void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
330                    CheckerContext &C) const;
331
332  void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
333                      CheckerContext &C,
334                      const StreamErrorState &ErrorKind) const;
335
336  void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call,
337                         CheckerContext &C,
338                         const StreamErrorState &ErrorKind) const;
339
340  /// Check that the stream (in StreamVal) is not NULL.
341  /// If it can only be NULL a sink node is generated and nullptr returned.
342  /// Otherwise the return value is a new state where the stream is constrained
343  /// to be non-null.
344  ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
345                                      CheckerContext &C,
346                                      ProgramStateRef State) const;
347
348  /// Check that the stream is the opened state.
349  /// If the stream is known to be not opened an error is generated
350  /// and nullptr returned, otherwise the original state is returned.
351  ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C,
352                                     ProgramStateRef State) const;
353
354  /// Check that the stream has not an invalid ("indeterminate") file position,
355  /// generate warning for it.
356  /// (EOF is not an invalid position.)
357  /// The returned state can be nullptr if a fatal error was generated.
358  /// It can return non-null state if the stream has not an invalid position or
359  /// there is execution path with non-invalid position.
360  ProgramStateRef
361  ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C,
362                                    ProgramStateRef State) const;
363
364  /// Check the legality of the 'whence' argument of 'fseek'.
365  /// Generate error and return nullptr if it is found to be illegal.
366  /// Otherwise returns the state.
367  /// (State is not changed here because the "whence" value is already known.)
368  ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
369                                           ProgramStateRef State) const;
370
371  /// Generate warning about stream in EOF state.
372  /// There will be always a state transition into the passed State,
373  /// by the new non-fatal error node or (if failed) a normal transition,
374  /// to ensure uniform handling.
375  void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
376                         ProgramStateRef State) const;
377
378  /// Emit resource leak warnings for the given symbols.
379  /// Createn a non-fatal error node for these, and returns it (if any warnings
380  /// were generated). Return value is non-null.
381  ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
382                            CheckerContext &C, ExplodedNode *Pred) const;
383
384  /// Find the description data of the function called by a call event.
385  /// Returns nullptr if no function is recognized.
386  const FnDescription *lookupFn(const CallEvent &Call) const {
387    // Recognize "global C functions" with only integral or pointer arguments
388    // (and matching name) as stream functions.
389    if (!Call.isGlobalCFunction())
390      return nullptr;
391    for (auto *P : Call.parameters()) {
392      QualType T = P->getType();
393      if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
394        return nullptr;
395    }
396
397    return FnDescriptions.lookup(Call);
398  }
399
400  /// Generate a message for BugReporterVisitor if the stored symbol is
401  /// marked as interesting by the actual bug report.
402  // FIXME: Use lambda instead.
403  struct NoteFn {
404    const BugType *BT_ResourceLeak;
405    SymbolRef StreamSym;
406    std::string Message;
407
408    std::string operator()(PathSensitiveBugReport &BR) const {
409      if (BR.isInteresting(StreamSym) && &BR.getBugType() == BT_ResourceLeak)
410        return Message;
411
412      return "";
413    }
414  };
415
416  const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym,
417                                  const std::string &Message) const {
418    return C.getNoteTag(NoteFn{&BT_ResourceLeak, StreamSym, Message});
419  }
420
421  const NoteTag *constructSetEofNoteTag(CheckerContext &C,
422                                        SymbolRef StreamSym) const {
423    return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) {
424      if (!BR.isInteresting(StreamSym) ||
425          &BR.getBugType() != this->getBT_StreamEof())
426        return "";
427
428      BR.markNotInteresting(StreamSym);
429
430      return "Assuming stream reaches end-of-file here";
431    });
432  }
433
434  void initEof(CheckerContext &C) const {
435    if (EofVal)
436      return;
437
438    if (const std::optional<int> OptInt =
439            tryExpandAsInteger("EOF", C.getPreprocessor()))
440      EofVal = *OptInt;
441    else
442      EofVal = -1;
443  }
444
445  /// Searches for the ExplodedNode where the file descriptor was acquired for
446  /// StreamSym.
447  static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
448                                                SymbolRef StreamSym,
449                                                CheckerContext &C);
450};
451
452} // end anonymous namespace
453
454// This map holds the state of a stream.
455// The stream is identified with a SymbolRef that is created when a stream
456// opening function is modeled by the checker.
457REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
458
459inline void assertStreamStateOpened(const StreamState *SS) {
460  assert(SS->isOpened() && "Stream is expected to be opened");
461}
462
463const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
464                                                      SymbolRef StreamSym,
465                                                      CheckerContext &C) {
466  ProgramStateRef State = N->getState();
467  // When bug type is resource leak, exploded node N may not have state info
468  // for leaked file descriptor, but predecessor should have it.
469  if (!State->get<StreamMap>(StreamSym))
470    N = N->getFirstPred();
471
472  const ExplodedNode *Pred = N;
473  while (N) {
474    State = N->getState();
475    if (!State->get<StreamMap>(StreamSym))
476      return Pred;
477    Pred = N;
478    N = N->getFirstPred();
479  }
480
481  return nullptr;
482}
483
484//===----------------------------------------------------------------------===//
485// Methods of StreamChecker.
486//===----------------------------------------------------------------------===//
487
488void StreamChecker::checkPreCall(const CallEvent &Call,
489                                 CheckerContext &C) const {
490  initEof(C);
491
492  const FnDescription *Desc = lookupFn(Call);
493  if (!Desc || !Desc->PreFn)
494    return;
495
496  Desc->PreFn(this, Desc, Call, C);
497}
498
499bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
500  const FnDescription *Desc = lookupFn(Call);
501  if (!Desc && TestMode)
502    Desc = FnTestDescriptions.lookup(Call);
503  if (!Desc || !Desc->EvalFn)
504    return false;
505
506  Desc->EvalFn(this, Desc, Call, C);
507
508  return C.isDifferent();
509}
510
511void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call,
512                              CheckerContext &C) const {
513  ProgramStateRef State = C.getState();
514  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
515  if (!CE)
516    return;
517
518  DefinedSVal RetVal = makeRetVal(C, CE);
519  SymbolRef RetSym = RetVal.getAsSymbol();
520  assert(RetSym && "RetVal must be a symbol here.");
521
522  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
523
524  // Bifurcate the state into two: one with a valid FILE* pointer, the other
525  // with a NULL.
526  ProgramStateRef StateNotNull, StateNull;
527  std::tie(StateNotNull, StateNull) =
528      C.getConstraintManager().assumeDual(State, RetVal);
529
530  StateNotNull =
531      StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
532  StateNull =
533      StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
534
535  C.addTransition(StateNotNull,
536                  constructNoteTag(C, RetSym, "Stream opened here"));
537  C.addTransition(StateNull);
538}
539
540void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call,
541                               CheckerContext &C) const {
542  // Do not allow NULL as passed stream pointer but allow a closed stream.
543  ProgramStateRef State = C.getState();
544  State = ensureStreamNonNull(getStreamArg(Desc, Call),
545                              Call.getArgExpr(Desc->StreamArgNo), C, State);
546  if (!State)
547    return;
548
549  C.addTransition(State);
550}
551
552void StreamChecker::evalFreopen(const FnDescription *Desc,
553                                const CallEvent &Call,
554                                CheckerContext &C) const {
555  ProgramStateRef State = C.getState();
556
557  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
558  if (!CE)
559    return;
560
561  std::optional<DefinedSVal> StreamVal =
562      getStreamArg(Desc, Call).getAs<DefinedSVal>();
563  if (!StreamVal)
564    return;
565
566  SymbolRef StreamSym = StreamVal->getAsSymbol();
567  // Do not care about concrete values for stream ("(FILE *)0x12345"?).
568  // FIXME: Can be stdin, stdout, stderr such values?
569  if (!StreamSym)
570    return;
571
572  // Do not handle untracked stream. It is probably escaped.
573  if (!State->get<StreamMap>(StreamSym))
574    return;
575
576  // Generate state for non-failed case.
577  // Return value is the passed stream pointer.
578  // According to the documentations, the stream is closed first
579  // but any close error is ignored. The state changes to (or remains) opened.
580  ProgramStateRef StateRetNotNull =
581      State->BindExpr(CE, C.getLocationContext(), *StreamVal);
582  // Generate state for NULL return value.
583  // Stream switches to OpenFailed state.
584  ProgramStateRef StateRetNull =
585      State->BindExpr(CE, C.getLocationContext(),
586                      C.getSValBuilder().makeNullWithType(CE->getType()));
587
588  StateRetNotNull =
589      StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
590  StateRetNull =
591      StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
592
593  C.addTransition(StateRetNotNull,
594                  constructNoteTag(C, StreamSym, "Stream reopened here"));
595  C.addTransition(StateRetNull);
596}
597
598void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
599                               CheckerContext &C) const {
600  ProgramStateRef State = C.getState();
601  SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
602  if (!Sym)
603    return;
604
605  const StreamState *SS = State->get<StreamMap>(Sym);
606  if (!SS)
607    return;
608
609  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
610  if (!CE)
611    return;
612
613  assertStreamStateOpened(SS);
614
615  // Close the File Descriptor.
616  // Regardless if the close fails or not, stream becomes "closed"
617  // and can not be used any more.
618  State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
619
620  // Return 0 on success, EOF on failure.
621  SValBuilder &SVB = C.getSValBuilder();
622  ProgramStateRef StateSuccess = State->BindExpr(
623      CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy));
624  ProgramStateRef StateFailure =
625      State->BindExpr(CE, C.getLocationContext(),
626                      SVB.makeIntVal(*EofVal, C.getASTContext().IntTy));
627
628  C.addTransition(StateSuccess);
629  C.addTransition(StateFailure);
630}
631
632void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call,
633                             CheckerContext &C) const {
634  ProgramStateRef State = C.getState();
635  SVal StreamVal = getStreamArg(Desc, Call);
636  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
637                              State);
638  if (!State)
639    return;
640  State = ensureStreamOpened(StreamVal, C, State);
641  if (!State)
642    return;
643  State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
644  if (!State)
645    return;
646
647  SymbolRef Sym = StreamVal.getAsSymbol();
648  if (Sym && State->get<StreamMap>(Sym)) {
649    const StreamState *SS = State->get<StreamMap>(Sym);
650    if (SS->ErrorState & ErrorFEof)
651      reportFEofWarning(Sym, C, State);
652  } else {
653    C.addTransition(State);
654  }
655}
656
657void StreamChecker::preFwrite(const FnDescription *Desc, const CallEvent &Call,
658                              CheckerContext &C) const {
659  ProgramStateRef State = C.getState();
660  SVal StreamVal = getStreamArg(Desc, Call);
661  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
662                              State);
663  if (!State)
664    return;
665  State = ensureStreamOpened(StreamVal, C, State);
666  if (!State)
667    return;
668  State = ensureNoFilePositionIndeterminate(StreamVal, C, State);
669  if (!State)
670    return;
671
672  C.addTransition(State);
673}
674
675void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
676                                    const CallEvent &Call, CheckerContext &C,
677                                    bool IsFread) const {
678  ProgramStateRef State = C.getState();
679  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
680  if (!StreamSym)
681    return;
682
683  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
684  if (!CE)
685    return;
686
687  std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
688  if (!SizeVal)
689    return;
690  std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>();
691  if (!NMembVal)
692    return;
693
694  const StreamState *OldSS = State->get<StreamMap>(StreamSym);
695  if (!OldSS)
696    return;
697
698  assertStreamStateOpened(OldSS);
699
700  // C'99 standard, ��7.19.8.1.3, the return value of fread:
701  // The fread function returns the number of elements successfully read, which
702  // may be less than nmemb if a read error or end-of-file is encountered. If
703  // size or nmemb is zero, fread returns zero and the contents of the array and
704  // the state of the stream remain unchanged.
705
706  if (State->isNull(*SizeVal).isConstrainedTrue() ||
707      State->isNull(*NMembVal).isConstrainedTrue()) {
708    // This is the "size or nmemb is zero" case.
709    // Just return 0, do nothing more (not clear the error flags).
710    State = bindInt(0, State, C, CE);
711    C.addTransition(State);
712    return;
713  }
714
715  // Generate a transition for the success state.
716  // If we know the state to be FEOF at fread, do not add a success state.
717  if (!IsFread || (OldSS->ErrorState != ErrorFEof)) {
718    ProgramStateRef StateNotFailed =
719        State->BindExpr(CE, C.getLocationContext(), *NMembVal);
720    StateNotFailed =
721        StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
722    C.addTransition(StateNotFailed);
723  }
724
725  // Add transition for the failed state.
726  NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
727  ProgramStateRef StateFailed =
728      State->BindExpr(CE, C.getLocationContext(), RetVal);
729  auto Cond =
730      C.getSValBuilder()
731          .evalBinOpNN(State, BO_LT, RetVal, *NMembVal, C.getASTContext().IntTy)
732          .getAs<DefinedOrUnknownSVal>();
733  if (!Cond)
734    return;
735  StateFailed = StateFailed->assume(*Cond, true);
736  if (!StateFailed)
737    return;
738
739  StreamErrorState NewES;
740  if (IsFread)
741    NewES =
742        (OldSS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError;
743  else
744    NewES = ErrorFError;
745  // If a (non-EOF) error occurs, the resulting value of the file position
746  // indicator for the stream is indeterminate.
747  StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
748  StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
749  if (IsFread && OldSS->ErrorState != ErrorFEof)
750    C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
751  else
752    C.addTransition(StateFailed);
753}
754
755void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
756                             CheckerContext &C) const {
757  ProgramStateRef State = C.getState();
758  SVal StreamVal = getStreamArg(Desc, Call);
759  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
760                              State);
761  if (!State)
762    return;
763  State = ensureStreamOpened(StreamVal, C, State);
764  if (!State)
765    return;
766  State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State);
767  if (!State)
768    return;
769
770  C.addTransition(State);
771}
772
773void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
774                              CheckerContext &C) const {
775  ProgramStateRef State = C.getState();
776  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
777  if (!StreamSym)
778    return;
779
780  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
781  if (!CE)
782    return;
783
784  // Ignore the call if the stream is not tracked.
785  if (!State->get<StreamMap>(StreamSym))
786    return;
787
788  DefinedSVal RetVal = makeRetVal(C, CE);
789
790  // Make expression result.
791  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
792
793  // Bifurcate the state into failed and non-failed.
794  // Return zero on success, nonzero on error.
795  ProgramStateRef StateNotFailed, StateFailed;
796  std::tie(StateFailed, StateNotFailed) =
797      C.getConstraintManager().assumeDual(State, RetVal);
798
799  // Reset the state to opened with no error.
800  StateNotFailed =
801      StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
802  // We get error.
803  // It is possible that fseek fails but sets none of the error flags.
804  // If fseek failed, assume that the file position becomes indeterminate in any
805  // case.
806  StateFailed = StateFailed->set<StreamMap>(
807      StreamSym,
808      StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true));
809
810  C.addTransition(StateNotFailed);
811  C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
812}
813
814void StreamChecker::evalFgetpos(const FnDescription *Desc,
815                                const CallEvent &Call,
816                                CheckerContext &C) const {
817  ProgramStateRef State = C.getState();
818  SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
819  if (!Sym)
820    return;
821
822  // Do not evaluate if stream is not found.
823  if (!State->get<StreamMap>(Sym))
824    return;
825
826  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
827  if (!CE)
828    return;
829
830  DefinedSVal RetVal = makeRetVal(C, CE);
831  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
832  ProgramStateRef StateNotFailed, StateFailed;
833  std::tie(StateFailed, StateNotFailed) =
834      C.getConstraintManager().assumeDual(State, RetVal);
835
836  // This function does not affect the stream state.
837  // Still we add success and failure state with the appropriate return value.
838  // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
839  C.addTransition(StateNotFailed);
840  C.addTransition(StateFailed);
841}
842
843void StreamChecker::evalFsetpos(const FnDescription *Desc,
844                                const CallEvent &Call,
845                                CheckerContext &C) const {
846  ProgramStateRef State = C.getState();
847  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
848  if (!StreamSym)
849    return;
850
851  const StreamState *SS = State->get<StreamMap>(StreamSym);
852  if (!SS)
853    return;
854
855  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
856  if (!CE)
857    return;
858
859  assertStreamStateOpened(SS);
860
861  DefinedSVal RetVal = makeRetVal(C, CE);
862  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
863  ProgramStateRef StateNotFailed, StateFailed;
864  std::tie(StateFailed, StateNotFailed) =
865      C.getConstraintManager().assumeDual(State, RetVal);
866
867  StateNotFailed = StateNotFailed->set<StreamMap>(
868      StreamSym, StreamState::getOpened(Desc, ErrorNone, false));
869
870  // At failure ferror could be set.
871  // The standards do not tell what happens with the file position at failure.
872  // But we can assume that it is dangerous to make a next I/O operation after
873  // the position was not set correctly (similar to 'fseek').
874  StateFailed = StateFailed->set<StreamMap>(
875      StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
876
877  C.addTransition(StateNotFailed);
878  C.addTransition(StateFailed);
879}
880
881void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
882                              CheckerContext &C) const {
883  ProgramStateRef State = C.getState();
884  SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
885  if (!Sym)
886    return;
887
888  if (!State->get<StreamMap>(Sym))
889    return;
890
891  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
892  if (!CE)
893    return;
894
895  SValBuilder &SVB = C.getSValBuilder();
896  NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
897  ProgramStateRef StateNotFailed =
898      State->BindExpr(CE, C.getLocationContext(), RetVal);
899  auto Cond = SVB.evalBinOp(State, BO_GE, RetVal,
900                            SVB.makeZeroVal(C.getASTContext().LongTy),
901                            SVB.getConditionType())
902                  .getAs<DefinedOrUnknownSVal>();
903  if (!Cond)
904    return;
905  StateNotFailed = StateNotFailed->assume(*Cond, true);
906  if (!StateNotFailed)
907    return;
908
909  ProgramStateRef StateFailed = State->BindExpr(
910      CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy));
911
912  C.addTransition(StateNotFailed);
913  C.addTransition(StateFailed);
914}
915
916void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
917                               CheckerContext &C) const {
918  ProgramStateRef State = C.getState();
919  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
920  if (!StreamSym)
921    return;
922
923  const StreamState *SS = State->get<StreamMap>(StreamSym);
924  if (!SS)
925    return;
926
927  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
928  if (!CE)
929    return;
930
931  assertStreamStateOpened(SS);
932
933  State = State->set<StreamMap>(StreamSym,
934                                StreamState::getOpened(Desc, ErrorNone, false));
935
936  C.addTransition(State);
937}
938
939void StreamChecker::evalClearerr(const FnDescription *Desc,
940                                 const CallEvent &Call,
941                                 CheckerContext &C) const {
942  ProgramStateRef State = C.getState();
943  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
944  if (!StreamSym)
945    return;
946
947  const StreamState *SS = State->get<StreamMap>(StreamSym);
948  if (!SS)
949    return;
950
951  assertStreamStateOpened(SS);
952
953  // FilePositionIndeterminate is not cleared.
954  State = State->set<StreamMap>(
955      StreamSym,
956      StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate));
957  C.addTransition(State);
958}
959
960void StreamChecker::evalFeofFerror(const FnDescription *Desc,
961                                   const CallEvent &Call, CheckerContext &C,
962                                   const StreamErrorState &ErrorKind) const {
963  ProgramStateRef State = C.getState();
964  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
965  if (!StreamSym)
966    return;
967
968  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
969  if (!CE)
970    return;
971
972  const StreamState *SS = State->get<StreamMap>(StreamSym);
973  if (!SS)
974    return;
975
976  assertStreamStateOpened(SS);
977
978  if (SS->ErrorState & ErrorKind) {
979    // Execution path with error of ErrorKind.
980    // Function returns true.
981    // From now on it is the only one error state.
982    ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE);
983    C.addTransition(TrueState->set<StreamMap>(
984        StreamSym, StreamState::getOpened(Desc, ErrorKind,
985                                          SS->FilePositionIndeterminate &&
986                                              !ErrorKind.isFEof())));
987  }
988  if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) {
989    // Execution path(s) with ErrorKind not set.
990    // Function returns false.
991    // New error state is everything before minus ErrorKind.
992    ProgramStateRef FalseState = bindInt(0, State, C, CE);
993    C.addTransition(FalseState->set<StreamMap>(
994        StreamSym,
995        StreamState::getOpened(
996            Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof())));
997  }
998}
999
1000void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
1001                               CheckerContext &C) const {
1002  ProgramStateRef State = C.getState();
1003  SVal StreamVal = getStreamArg(Desc, Call);
1004  State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
1005                              State);
1006  if (!State)
1007    return;
1008  State = ensureStreamOpened(StreamVal, C, State);
1009  if (!State)
1010    return;
1011
1012  C.addTransition(State);
1013}
1014
1015void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
1016                                      const CallEvent &Call, CheckerContext &C,
1017                                      const StreamErrorState &ErrorKind) const {
1018  ProgramStateRef State = C.getState();
1019  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
1020  assert(StreamSym && "Operation not permitted on non-symbolic stream value.");
1021  const StreamState *SS = State->get<StreamMap>(StreamSym);
1022  assert(SS && "Stream should be tracked by the checker.");
1023  State = State->set<StreamMap>(
1024      StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind));
1025  C.addTransition(State);
1026}
1027
1028ProgramStateRef
1029StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE,
1030                                   CheckerContext &C,
1031                                   ProgramStateRef State) const {
1032  auto Stream = StreamVal.getAs<DefinedSVal>();
1033  if (!Stream)
1034    return State;
1035
1036  ConstraintManager &CM = C.getConstraintManager();
1037
1038  ProgramStateRef StateNotNull, StateNull;
1039  std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream);
1040
1041  if (!StateNotNull && StateNull) {
1042    // Stream argument is NULL, stop analysis on this path.
1043    // This case should occur only if StdLibraryFunctionsChecker (or ModelPOSIX
1044    // option of it) is not turned on, otherwise that checker ensures non-null
1045    // argument.
1046    C.generateSink(StateNull, C.getPredecessor());
1047    return nullptr;
1048  }
1049
1050  return StateNotNull;
1051}
1052
1053ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal,
1054                                                  CheckerContext &C,
1055                                                  ProgramStateRef State) const {
1056  SymbolRef Sym = StreamVal.getAsSymbol();
1057  if (!Sym)
1058    return State;
1059
1060  const StreamState *SS = State->get<StreamMap>(Sym);
1061  if (!SS)
1062    return State;
1063
1064  if (SS->isClosed()) {
1065    // Using a stream pointer after 'fclose' causes undefined behavior
1066    // according to cppreference.com .
1067    ExplodedNode *N = C.generateErrorNode();
1068    if (N) {
1069      C.emitReport(std::make_unique<PathSensitiveBugReport>(
1070          BT_UseAfterClose,
1071          "Stream might be already closed. Causes undefined behaviour.", N));
1072      return nullptr;
1073    }
1074
1075    return State;
1076  }
1077
1078  if (SS->isOpenFailed()) {
1079    // Using a stream that has failed to open is likely to cause problems.
1080    // This should usually not occur because stream pointer is NULL.
1081    // But freopen can cause a state when stream pointer remains non-null but
1082    // failed to open.
1083    ExplodedNode *N = C.generateErrorNode();
1084    if (N) {
1085      C.emitReport(std::make_unique<PathSensitiveBugReport>(
1086          BT_UseAfterOpenFailed,
1087          "Stream might be invalid after "
1088          "(re-)opening it has failed. "
1089          "Can cause undefined behaviour.",
1090          N));
1091      return nullptr;
1092    }
1093    return State;
1094  }
1095
1096  return State;
1097}
1098
1099ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate(
1100    SVal StreamVal, CheckerContext &C, ProgramStateRef State) const {
1101  static const char *BugMessage =
1102      "File position of the stream might be 'indeterminate' "
1103      "after a failed operation. "
1104      "Can cause undefined behavior.";
1105
1106  SymbolRef Sym = StreamVal.getAsSymbol();
1107  if (!Sym)
1108    return State;
1109
1110  const StreamState *SS = State->get<StreamMap>(Sym);
1111  if (!SS)
1112    return State;
1113
1114  assert(SS->isOpened() && "First ensure that stream is opened.");
1115
1116  if (SS->FilePositionIndeterminate) {
1117    if (SS->ErrorState & ErrorFEof) {
1118      // The error is unknown but may be FEOF.
1119      // Continue analysis with the FEOF error state.
1120      // Report warning because the other possible error states.
1121      ExplodedNode *N = C.generateNonFatalErrorNode(State);
1122      if (!N)
1123        return nullptr;
1124
1125      C.emitReport(std::make_unique<PathSensitiveBugReport>(
1126          BT_IndeterminatePosition, BugMessage, N));
1127      return State->set<StreamMap>(
1128          Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false));
1129    }
1130
1131    // Known or unknown error state without FEOF possible.
1132    // Stop analysis, report error.
1133    ExplodedNode *N = C.generateErrorNode(State);
1134    if (N)
1135      C.emitReport(std::make_unique<PathSensitiveBugReport>(
1136          BT_IndeterminatePosition, BugMessage, N));
1137
1138    return nullptr;
1139  }
1140
1141  return State;
1142}
1143
1144ProgramStateRef
1145StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
1146                                        ProgramStateRef State) const {
1147  std::optional<nonloc::ConcreteInt> CI =
1148      WhenceVal.getAs<nonloc::ConcreteInt>();
1149  if (!CI)
1150    return State;
1151
1152  int64_t X = CI->getValue().getSExtValue();
1153  if (X >= 0 && X <= 2)
1154    return State;
1155
1156  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1157    C.emitReport(std::make_unique<PathSensitiveBugReport>(
1158        BT_IllegalWhence,
1159        "The whence argument to fseek() should be "
1160        "SEEK_SET, SEEK_END, or SEEK_CUR.",
1161        N));
1162    return nullptr;
1163  }
1164
1165  return State;
1166}
1167
1168void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C,
1169                                      ProgramStateRef State) const {
1170  if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
1171    auto R = std::make_unique<PathSensitiveBugReport>(
1172        BT_StreamEof,
1173        "Read function called when stream is in EOF state. "
1174        "Function has no effect.",
1175        N);
1176    R->markInteresting(StreamSym);
1177    C.emitReport(std::move(R));
1178    return;
1179  }
1180  C.addTransition(State);
1181}
1182
1183ExplodedNode *
1184StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms,
1185                           CheckerContext &C, ExplodedNode *Pred) const {
1186  ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred);
1187  if (!Err)
1188    return Pred;
1189
1190  for (SymbolRef LeakSym : LeakedSyms) {
1191    // Resource leaks can result in multiple warning that describe the same kind
1192    // of programming error:
1193    //  void f() {
1194    //    FILE *F = fopen("a.txt");
1195    //    if (rand()) // state split
1196    //      return; // warning
1197    //  } // warning
1198    // While this isn't necessarily true (leaking the same stream could result
1199    // from a different kinds of errors), the reduction in redundant reports
1200    // makes this a worthwhile heuristic.
1201    // FIXME: Add a checker option to turn this uniqueing feature off.
1202    const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C);
1203    assert(StreamOpenNode && "Could not find place of stream opening.");
1204    PathDiagnosticLocation LocUsedForUniqueing =
1205        PathDiagnosticLocation::createBegin(
1206            StreamOpenNode->getStmtForDiagnostics(), C.getSourceManager(),
1207            StreamOpenNode->getLocationContext());
1208
1209    std::unique_ptr<PathSensitiveBugReport> R =
1210        std::make_unique<PathSensitiveBugReport>(
1211            BT_ResourceLeak,
1212            "Opened stream never closed. Potential resource leak.", Err,
1213            LocUsedForUniqueing,
1214            StreamOpenNode->getLocationContext()->getDecl());
1215    R->markInteresting(LeakSym);
1216    C.emitReport(std::move(R));
1217  }
1218
1219  return Err;
1220}
1221
1222void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1223                                     CheckerContext &C) const {
1224  ProgramStateRef State = C.getState();
1225
1226  llvm::SmallVector<SymbolRef, 2> LeakedSyms;
1227
1228  const StreamMapTy &Map = State->get<StreamMap>();
1229  for (const auto &I : Map) {
1230    SymbolRef Sym = I.first;
1231    const StreamState &SS = I.second;
1232    if (!SymReaper.isDead(Sym))
1233      continue;
1234    if (SS.isOpened())
1235      LeakedSyms.push_back(Sym);
1236    State = State->remove<StreamMap>(Sym);
1237  }
1238
1239  ExplodedNode *N = C.getPredecessor();
1240  if (!LeakedSyms.empty())
1241    N = reportLeaks(LeakedSyms, C, N);
1242
1243  C.addTransition(State, N);
1244}
1245
1246ProgramStateRef StreamChecker::checkPointerEscape(
1247    ProgramStateRef State, const InvalidatedSymbols &Escaped,
1248    const CallEvent *Call, PointerEscapeKind Kind) const {
1249  // Check for file-handling system call that is not handled by the checker.
1250  // FIXME: The checker should be updated to handle all system calls that take
1251  // 'FILE*' argument. These are now ignored.
1252  if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader())
1253    return State;
1254
1255  for (SymbolRef Sym : Escaped) {
1256    // The symbol escaped.
1257    // From now the stream can be manipulated in unknown way to the checker,
1258    // it is not possible to handle it any more.
1259    // Optimistically, assume that the corresponding file handle will be closed
1260    // somewhere else.
1261    // Remove symbol from state so the following stream calls on this symbol are
1262    // not handled by the checker.
1263    State = State->remove<StreamMap>(Sym);
1264  }
1265  return State;
1266}
1267
1268//===----------------------------------------------------------------------===//
1269// Checker registration.
1270//===----------------------------------------------------------------------===//
1271
1272void ento::registerStreamChecker(CheckerManager &Mgr) {
1273  Mgr.registerChecker<StreamChecker>();
1274}
1275
1276bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {
1277  return true;
1278}
1279
1280void ento::registerStreamTesterChecker(CheckerManager &Mgr) {
1281  auto *Checker = Mgr.getChecker<StreamChecker>();
1282  Checker->TestMode = true;
1283}
1284
1285bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) {
1286  return true;
1287}
1288